Our CI server right now can deploy component to local/dev easerver, and export it as the jar file to be deploy to acceptance or production later. We don’t want use Nant to do production depoy, basic dos command should be used instead.

Result we try to achieve : One click deploy command to target server.

Tricks:

  1. Use “start /wait /min ” syntax to call jagtool to enable waiting for the deploy process.
  2. Add an exit command in jagtool.bat, to allow this separated window to close after it finished.
  3. Merge the log result into a single log file, jagtool by default always overwrite the existing file, not append.
  4. Loop through the jar folder.

Here is the dos command I end up with.


@echo off
@echo #########################################################
@echo   You must logon to target server to run this command
@echo #########################################################

if NOT Exist deploy.log (
  echo > deploy.log
)

for /f %%a IN ('dir /b *.jar') do (
  echo "Deploying %%a"   >deploy.log
  start /WAIT /MIN jagtool2  -local -logfile tmp.log deploy -type jagjar -jagjartype Package %%a
  copy deploy.log+tmp.log
)

del tmp.log

@echo on
pause
R8603284386

I read a post about MEF vs. IOC which cleared the confusion: MEF should be used external of app, while IOC should be used internally.

Good point. I have been struggling with how to separate my view and view model correctly in WPF app for a long time.   In fact Bil Simer already suggested that MEF is perfect fit in MVVM/Presentation Model last year during the Edmonton Code Camp 2008.

Ideally, all view models can be compiled into a separated assembly and put into specific folder to be imported by UI views!

Some code changes during my code conversion include:

  • Remove ctor args from view model, I haven’t figure out how to feed ctor args when MEF importing. This is not easy as auto-wiring in IOC. I ended up with a separate initialize method in each view model, which is not too bad, because I have to call wireUpViewModel from view anyway, this Initialize method has a perfect place to put.
  • I’m still not sure where should I put Container.Compose() method, because this method needs the instance the object to be imported into, I think compose as needed is better for WPF app.
  • Shared mode is the default behavior for export and import, it messed up my event handling code, (true, I didn’t do -= before +=). I had to explicitly add this everywhere.
[Export]
[PartCreationPolicy(CreationPolicy.NonShared)]
or
[Import(typeof(NewRequestTypeViewModel), RequiredCreationPolicy = CreationPolicy.NonShared)]

Code:

    public partial class TemplateEditShell : Window{

        private CompositionContainer _mefContainer;

        public TemplateEditShell()
        {
            InitializeComponent();
            InitialzieMEF();
        }

        private void InitialzieMEF()
        {
            var catalog = new AssemblyCatalog(System.Reflection.Assembly.GetExecutingAssembly());
            _mefContainer = new CompositionContainer(catalog);
        }

        private void btnEditItemList_Click(object sender, RoutedEventArgs e)
        {
            var proxy = ObjectFactory.GetInstance<IRequestManagementAdminService>();
            var view = new EditRequestTypeItemListView(_viewModel.SelectedRequestType, proxy);
            _mefContainer.ComposeParts(view);
            view.WireUpViewModel();
            view.ShowDialog();
        }
  }

    public partial class EditRequestTypeItemListView : Window
    {
        private readonly RequestTypeDto _requestTypeDto;
        private readonly IRequestManagementAdminService _proxy;

        [Import]
        public IEditRequestTypeItemListViewModel EditRequestTypeItemListViewModel { get; set; }

        public EditRequestTypeItemListView(RequestTypeDto requestTypeDto, IRequestManagementAdminservice proxy)
        {
            _requestTypeDto = requestTypeDto;
            _proxy = proxy;
            InitializeComponent();

            // Can't wire up in ctor, coz MEF import hasn't start/finish yet.
//            WireUpViewModel();
        }

        public void WireUpViewModel()
        {
            DataContext = EditRequestTypeItemListViewModel;

            EditRequestTypeItemListViewModel.Initialize(_proxy, _requestTypeDto);

            EditRequestTypeItemListViewModel.RequestShowMessage += (o, arg) => MessageBox.Show(arg.EventData);

        }
    }

    [Export(typeof(IEditRequestTypeItemListViewModel))]
    public class EditRequestTypeItemListViewModel : ViewModelBase, IEditRequestTypeItemListViewModel
    {
        private IRequestManagementAdminContract _proxy;
        private RequestTypeDto _currentRequestType;

        public RequestTypeItemDto SelectedRequestTypeItem { get; set; }

        public ObservableCollection ItemList { get; set; }

        public EditRequestTypeItemListViewModel()
        {
        }

        public void Initialize(IRequestManagementAdminContract proxy, RequestTypeDto currentRequestType)
        {
            _proxy = proxy;
            _currentRequestType = currentRequestType;

            ItemList = new ObservableCollection(proxy.FindAllRequestTypeItems(currentRequestType.Id));

            SelectedRequestTypeItem = ItemList.FirstOrDefault();
        }
  }

In case you are a poor PB developer still working on the dead EAF framework., here is the solution to make your life a little bit funner.

Unit-test IM

Lib List:

c:\pb_projects\testApp\src\server\testApp\pb112\integration-test\im_testApp_test.pbl;
c:\pb_projects\testApp\lib\my_client.pbd;
c:\pb_projects\testApp\lib\ext_client.pbd;
c:\pb_projects\testApp\lib\eaf_client.pbd;
c:\pb_projects\testApp\lib\my_shared.pbd;
c:\pb_projects\testApp\lib\eaf_shared.pbd;
c:\pb_projects\testApp\lib\ext_shared.pbd;
c:\pb_projects\testApp\test\tools\pbunit\pbunit.pbd;
c:\pb_projects\testApp\test\tools\pbunit\pbunitfunc.pbd;
c:\pb_projects\testApp\lib\my_eaf_ext_layer_rc.pbl;
c:\pb_projects\testApp\lib\eaf_proxies.pbd;

PBUnit test code:


//instance
n_connection icn_easerver
n_cst_interfaceservice inv_is

//setup

long ll_rc

constant string INI_FILE = "app.ini"

icn_easerver = CREATE n_connection
icn_easerver.driver = "jaguar"
icn_easerver.userID = ProfileString(INI_FILE, "EAServer", "UserID", "jagadmin")
icn_easerver.password = ProfileString(INI_FILE, "EAServer", "Password", "")
icn_easerver.options = ProfileString(INI_FILE, "EAServer", "Options", "")
icn_easerver.location = ProfileString(INI_FILE, "EAServer", "Server", "")+ ":" + ProfileString(INI_FILE, "EAServer", "Port", "")

ll_rc = icn_easerver.ConnectToServer()

IF ll_rc <> 0 THEN
fail("Connection failed: " + string(ll_rc))
return
END IF

inv_is = create n_cst_interfaceservice

inv_is.of_SetRequestor(This)
ll_rc = inv_is.setConnection(icn_EAServer)

inv_is.of_setUserID("jagadmin")
ll_rc = inv_is.setInterfaceManagerName("MyPackage/MyComponent")

// test dst without arg
long ll_rc

datastore lds_target
lds_target = create datastore

ll_rc = inv_is.registerdataset( "session_list" ,lds_target, "fullalways")

ll_rc = inv_is.retrieve("session_list" )

assert(ll_rc = 1)

assert(lds_target.rowcount() > 0)

//test dst with arg
long ll_rc

datastore lds_target
lds_target = create datastore

ll_rc = inv_is.registerdataset( "reg_activity" ,lds_target, "fullalways")
inv_is.addArgument( "reg_activity", "lastndays", 500  )

ll_rc = inv_is.retrieve("reg_activity" )

assert(ll_rc = 1)

assert(lds_target.rowcount() > 0)

We are on the process of applying continuous integration to all our powerbuilder projects. One of the challenge is the classic orcascript sometimes failed in bootstrap pbls. e.g. customer exception. The interesting thing is, I can see the objects in the pbl by using yBrowser 10. but the object contains customer exception is missing from pbl when I try to look it in PowerBuilder IDE.

Anyway, PowerGen definately can save me out of this trouble. It’s slow comparing to orca because it’s checking all the depenanct when bootstrapping, but it’s stable.

Something I think PowerGen can improve in the future:

  1. Allow passing paramters into, so I can still use orca to build executable with version number. PowerGen implement this by using their version edit product to do the post build action.
  2. Work path should be dynamic, right now it’s hard-coded in gen file. I have to standardize it.

Some other reasons for using orca besides PowerGen:

  1. to deploy EAServer component.
  2. orca import is very fast than synchronizing pbl from exported sr* file in PowerGen. This is a must-have feature to branch-merge in subversion, developers should be able to do this without PowerGen installed.

When creating orcascipt, usually I start from the existing PBT file which contains the huge lib list:

LibList “myapp_appl.pbl\\myapp_appl.pbl;myapp_attachment.pbl\\myapp_attachment.pbl;myapp_br.pbl\\myapp_br.pbl;myapp_casefile.pbl\\myapp_casefile.pbl;myapp_casefile_related.pbl\\myapp_casefile_related.pbl;myapp_dddw.pbl\\myapp_dddw.pbl;myapp_disciplinary_action.pbl\\myapp_disciplinary_action.pbl;myapp_licensee.pbl\\myapp_licensee.pbl;myapp_list.pbl\\myapp_list.pbl;myapp_mass_entry.pbl\\myapp_mass_entry.pbl;myapp_registration.pbl\\myapp_registration.pbl;myapp_report.pbl\\myapp_report.pbl;myapp_resource.pbl\\myapp_resource.pbl;myapp_schedule.pbl\\myapp_schedule.pbl;myapp_security.pbl\\myapp_security.pbl;myapp_ticket.pbl\\myapp_ticket.pbl;im_lls_rc_dws.pbl\\im_lls_rc_dws.pbl;im_lls_rc_rpt_dws.pbl\\im_lls_rc_rpt_dws.pbl;pies_shared.pbl\\pies_shared.pbl;reg_01.pbl\\reg_01.pbl;”

I want the orca format like this:

build executable “myapp.exe” “myapp.ico” “myapp.pbr” “yyyyyyyyyyyyyyyyyyy”

build Library “myapp_appl.pbl\myapp_appl.pbl” “” PBD
build Library “myapp_attachment.pbl\myapp_attachment.pbl” “” PBD
build Library “myapp_br.pbl\myapp_br.pbl” “” PBD
build Library “myapp_casefile.pbl\myapp_casefile.pbl” “” PBD
build Library “myapp_casefile_related.pbl\myapp_casefile_related.pbl” “” PBD
build Library “myapp_dddw.pbl\myapp_dddw.pbl” “” PBD
build Library “myapp_disciplinary_action.pbl\myapp_disciplinary_action.pbl” “” PBD
build Library “myapp_licensee.pbl\myapp_licensee.pbl” “” PBD
build Library “myapp_list.pbl\myapp_list.pbl” “” PBD
build Library “myapp_mass_entry.pbl\myapp_mass_entry.pbl” “” PBD
build Library “myapp_registration.pbl\myapp_registration.pbl” “” PBD
build Library “myapp_report.pbl\myapp_report.pbl” “” PBD
build Library “myapp_resource.pbl\myapp_resource.pbl” “” PBD
build Library “myapp_schedule.pbl\myapp_schedule.pbl” “” PBD
build Library “myapp_security.pbl\myapp_security.pbl” “” PBD
build Library “myapp_ticket.pbl\myapp_ticket.pbl” “” PBD
build Library “im_lls_rc_dws.pbl\im_lls_rc_dws.pbl” “” PBD
build Library “im_lls_rc_rpt_dws.pbl\im_lls_rc_rpt_dws.pbl” “” PBD
build Library “pies_shared.pbl\pies_shared.pbl” “” PBD
build Library “reg_01.pbl\reg_01.pbl” “” PBD

Here is how I did it in minutes of work, using NotePad++ relace with regexp:

  1. Replace step 1, this is easy, replace all \\ with \, and ‘;’ with “\n”
  2. Replace step 2, using regexp, replace all
    ^(.*)
    with
    build Library “\1″ “” PBD
  3. Done.

Notepad++ reference: http://notepad-plus.sourceforge.net/uk/regExpList.php

I wish we could use JIRA for generating release file in build result. cc.net does have a ModificationHistory publisher, but for some reason, the version CCConfig I have doesn’t work for this. (Somebody filed the bug already, I did a vote.)

The workaround I’m using:

  1. Enable ModificationWriterTask in cc.net project publisher, this will generate a file named modifications.xml in artifact folder, which only contains the changes since last build.
  2. Create a NAnt task to merge modifications info into a modificationHistory.xml file. (idea from here)
  3. Create another NAnt task to transform the xml into a text file.
	<target name="publish.mod.history" depends="merge.mod.history" >
		<style style="${build.dir}\modifications.xsl"
			in="${CCNetArtifactDirectory}\modificationHistory.xml"
			out="${CCNetArtifactDirectory}\${application.version}\modifications.txt">
	    <parameters>
	        <parameter name="reportType" namespaceuri="" value="Plain" />
	    </parameters>
		</style>
	</target>

	<target name="merge.mod.history" description="add the new mod list to an existing xml file">
		<property name="xmlnodes" value=""/>
		<xmlpeek xpath="//ArrayOfModification" file="${CCNetArtifactDirectory}\modificationHistory.xml" property="xmlnodes"></xmlpeek>
		<property name="newnode" value="" />
		<xmlpeek xpath="//ArrayOfModification" file="${CCNetArtifactDirectory}\modifications.xml" property="newnode"></xmlpeek>
		<property name="xmlnodes" value="${newnode}${xmlnodes}" />
		<xmlpoke file="${CCNetArtifactDirectory}\modificationHistory.xml" xpath="//ArrayOfModification" value="${xmlnodes}" />
	</target>

Modifications.xsl:

<?xml version="1.0"?><!-- DWXMLSource="modificationHistory0.xml" -->
<xsl:stylesheet
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"

     version="1.0">

    <xsl:output method="text"/>

    <xsl:variable name="modification.list" select="ArrayOfModification/Modification"/>

    <xsl:template match="/">
Modifications since last build (<xsl:value-of select="count($modification.list)"/>)
    <xsl:apply-templates select="$modification.list">
        <xsl:sort select="date" order="descending" data-type="text" />
    </xsl:apply-templates>
    </xsl:template>

    <!-- Modifications template -->
    <xsl:template match="Modification">
 (Revision:<xsl:value-of select="ChangeNumber"/>) <xsl:value-of select="FolderName"/>/<xsl:value-of select="FileName"/>, <xsl:value-of select="UserName"/> on <xsl:value-of select='substring(ModifiedTime, 0, 11)'/>, <xsl:value-of select="Comment"/>
    </xsl:template>

</xsl:stylesheet>

Result:

Modifications since last build (14)

(Revision:15) /trunk/build/modificationHistory0.xml, FMao on 2009-10-15,
(Revision:15) /trunk/build/modifications.xsl, FMao on 2009-10-15,
(Revision:12) /trunk/build/default.build, FMao on 2009-10-15, added new task to merge mod hist and transform xml
(Revision:12) /trunk/pbunit_demo.pbw, FMao on 2009-10-15, added new task to merge mod hist and transform xml

I wish WCF can provide something like this:

instead of

[PrincipalPermission(SecurityAction.Demand, Role ="Administrators")]

We can use

[PrincipalPermission(SecurityAction.Demand, RoleProvider = typeof(MyAuthorizationChecker)]

So developers won’t care if security requirement changed.

I posted before about explicitly calling

permissionSet.Demand()

right in service method, it works, but it would be nice to use service behavior attribute to make code looks cleaner.

Extending WCF service is not that hard, thanks to this post.

    public class CheckAuthorizationBehaviorAttribute : Attribute, IOperationBehavior
    {
        public string MethodName { get; set; }

        public void Validate(OperationDescription operationDescription)
        {
        }

        public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
        {
            dispatchOperation.Invoker = new AuthorizationChecker(dispatchOperation.Invoker
                , MethodName);
        }

        public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
        {

        }

        public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
        {

        }
    }

    public class AuthorizationChecker : IOperationInvoker
    {
        private readonly IOperationInvoker _invoker;
        private readonly string _methodName;

        public AuthorizationChecker(IOperationInvoker invoker, string methodName)
        {
            _invoker = invoker;
            _methodName = methodName;
        }

        public object[] AllocateInputs()
        {
            return _invoker.AllocateInputs();
        }

        public object Invoke(object instance, object[] inputs, out object[] outputs)
        {
             Console.WriteLine("Checking permission on methodname {0}.{1}", instance.ToString(),_methodName);

            if (!.....(my authorization logic here ))
                throw new FaultException<SecurityAccessDeniedException>(new SecurityAccessDeniedException("No access"))
                ;

            string value;
            value = (string)_invoker.Invoke(instance, inputs, out outputs);
            return value;

        }

        public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
        {
            return _invoker.InvokeBegin(instance, inputs, callback, state);
        }

        public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
        {
            return _invoker.InvokeEnd(instance, out outputs, result);
        }

        public bool IsSynchronous
        {
            get { return _invoker.IsSynchronous; }
        }
    }

Then my service contract is just like this:

        [ServiceContract]
        public interface ITestService
        {
            [CheckAuthorizationBehavior(MethodName = "PingWithUserName")]
            [OperationContract]
            void Ping(string username);

I was having difficult to get the method name inside invoker, the methodname is hide in the private syncInvoker class, so I had to add the public attribute to my behavior. Later I found out this might be a better idea, because what if the method is an overload one?

Still, it would be nice to set the default method name to the one we just attached. Hope I can figure this out soon.

This should be a simple configuration trick, just do it, increasing client side app.config.

            var binding = new WSHttpBinding();
            binding.MaxReceivedMessageSize = 2147483647;
            XmlDictionaryReaderQuotas readerQuotas = XmlDictionaryReaderQuotas.Max;

            binding.ReaderQuotas = readerQuotas;
            channelFactory = new ChannelFactory<IRequestManagementService>(
                binding,
                new EndpointAddress(_url));

             var _proxy = channelFactory.CreateChannel();

But this isn’t enough, you need to change the same setting on serverside as well. Unfortunately, the default web.config auto-generated by VS doesn’t have binding section at all, so you have to add this by your own. Here is an example:

  <system.serviceModel>
    <services>
      <service behaviorConfiguration="..."
        name="..."
               >
        <endpoint address="" binding="wsHttpBinding"
                  bindingConfiguration="maxStringBinding"
                  contract="I...Service">

          <identity>
            <dns value="localhost" />
          </identity>
        </endpoint>
        <endpoint address="soap" binding="basicHttpBinding" contract="I...Service" />
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />

      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior>...        </behavior>
      </serviceBehaviors>
    </behaviors>
    <bindings>
      <wsHttpBinding>
        <binding
         name="maxStringBinding"
         maxReceivedMessageSize="2147483647">
          <readerQuotas
            maxDepth="2147483647"
            maxStringContentLength="2147483647"
            maxArrayLength="2147483647"
            maxBytesPerRead="2147483647"
            maxNameTableCharCount="2147483647" />
        </binding>
      </wsHttpBinding>
    </bindings>
  </system.serviceModel>

When using Cruisecontrol.net + EAserver, we want to use pull instead of push style to deploy packages upto EAServer. Because the Cruisecontrol stores all the build result into the artifacts folder, what we need is a dos command can pull the specific version then deploy to EAServer. Here is the command I created:


@echo off
set artifacts_dir=\\ccnet\Work_Dir\Artifacts\my_project
cls
echo *****************************************
echo Please type in the version to deploy:
echo *****************************************
set /p version_number=
echo *****************************************
echo Starting to deploy %version_number%
echo *****************************************
if  "%version_number%"=="" goto help

pause

call jagtool -local delete Package:MyPackage
call jagtool -local deploy -type jagjar -jagjartype Package  %artifacts_dir%\%version_number%\jar\MyPackage.jar

pause

exit /b

:help
echo You need to specify the version to deploy
pause

EAserver acts as a client to consume WCF SOAP WebService, wrap it in WebDatawindow accessed by JSP web user.

Powerbuilder richclient can also directly consume WCF SOAP webservice, or access the EAF component which calls WebService.

Hybrid_app

The PB we are using 11.2 still have some limitation on webservice datawindow:

  1. Doesn’t support long type parameter, we had to change it to string.
  2. wsdl is wrapped in datawindow is not configurable, might be better if can change it through ini or dw property.

When deploying webservice datawindwo upto EAServer, should copy those auto-gened dlls to EAServer/bin folder, not /dll.

How to switch wsdl in dw?

  1. dw.Describe/Modify or dw.Create(syntax) won’t work for EAF/component, it keeps killing EAServer.
  2. Re-compile the auto-gened temp proxy cs file, like this post did. The interesting part is, the proxy’s ctor is already trying to read default configuration from some ini/config file:
    public RequestManagementService() {
                string urlSetting = System.Configuration.ConfigurationManager.AppSettings["EndpointURL"];
                if ((urlSetting != null)) {
                    this.Url = urlSetting;
                }
                else {
                    this.Url = "http://appdev01/RequestManagement/RequestService.svc/soap";
                }
            }
    

    My question is, can’t we create a config file to intercept this ctor reading?

    
    <configuration>
         <appSettings>
              <add key="EndpointURL" value="http://appdev02/RequestManagement/RequestService.svc/soap" />
         </appSettings>
    </configuration>
    

    Yes, that’s the place you can control to switch wsdl, not the one shown in dw syntax. In fact, if you change the wsdl in dw to an invalid one, it still works!

    How to control it? Create a config file named as your_app.exe.config, paste the configuration content in it. That’s it.
    Thanks to this post helping me out by showing how to get the current configuration file path: AppDomain.CurrentDomain.SetupInformation.ConfigurationFile

I’m using PBUnit, so the config file for testing is called pbunit.exe.config, when I deployed it upto EAserver, the config file should rename to jagsrv.exe.config.

If the whole EAServer is only using one webservice, this solution works; if you have multiple webservice wsdl to consume, you have to add more config settings by re-compiling this temp proxy file in /TmpWebService folder.