Friday 24 June 2016

A couple of notes of the automatic generation of a SOA Suite/OSB domain

Earlier you could have enjoyed my article on the automatic generation of a SOA/OSB domain. Earlier this week I encountered some issues with a domain created at a customer this way.

I got the change to dive into that this week and luckily not only I learned a lot again, but I found the problem as well. I adapted my scripts. I won't repost them completely, I've created a github account, and try to place them there in the near future.

But I'll cover the changes, especially those that caused the problems.

Enrollment


When you create a domain using the config.sh/.cmd wizard, and choose to configure the nodemanager, you'll be asked for the nodemanager user and password. You'll get a per domain nodemanager for free and the domain is enrolled for the nodemanager for you. You might need to adapt the nodemanager.properties in <domain_home>/nodemanager and set the property SecureListener to false. The default is true, while in the Machine definition in the domain the nodemanager/machine is configured to SSL. If you disable the SecureListener, you need to set the property in the wls-console under Machines to Plain instead of SSL. And adapt the listener-port.

The docs on configuring the nodemanager for 12.2.1 can be found here. One of the biggest changes between 11g and 12cR1 is that in 12cR2 you get a per domain nodemanager, with a nodemanager home in the domain home, instead of a nodemanager home in the FMW_HOME, the home of the binaries. Also in the bin folder of the domain you'll find scripts to start and stop the nodemanager.

So for a domain configured in the config-wizard, you need little to do to get the nodemanager working.

But using the scripts in my previous article, you'll find that although a nodemanager password file is created, something is missed for a proper startup of your servers using the nodemanager. You'll find that although the nodemanager starts, connecting to it using nmConnect(), fails with: a message like:
WLSTException: Error occured while performing nmConnect : Cannot connect to Node Manager. : Access to domain 'osb_domain' for user 'weblogic' denied.

You can go to the Domain->security settings in the weblogic console, and change the nodemanager password, but apparently this is not enough. You'll need to explicitly enroll the domain against the nodemanager. To do so:
  • Start the AdminServer, using the startWebLogic.sh/.cmd script in the domain home.
  • Start wlst.sh/.cmd
  • Connect to the AdminServer using:  connect(adminUser, adminPwd, adminURL)
  • Perform an NodeManager Enroll using: nmEnroll(soaDomainHome, nodeManagerHome)
  •  Stop the AdminServer
  • (Re)Start the nodemanager
  • In wlst: Connect to the nodemanager using nmConnect(....)
  • Perform nmStart('AdminServer')
The following enrollDomain.py script might help:
#############################################################################
# Create a SOA/BPM/OSB domain
#
# @author Martien van den Akker, Darwin-IT Professionals
# @version 1.1, 2016-06-23
#
#############################################################################
# Modify these values as necessary
import sys, traceback
scriptName = 'enrollDomain.py'
#
#Home Folders
soaDomainHome   = domainsHome+'/'+soaDomainName
nodeManagerHome = soaDomainHome+'/'+'nodemanager'
#
#
lineSeperator='__________________________________________________________________________________'
#
#
def usage():
  print 'Call script as: '
  print 'Windows: wlst.cmd '+scriptName+' -loadProperties localhost.properties'
  print 'Linux: wlst.sh '+scriptName+' -loadProperties environment.properties'
  print 'Property file should contain the following properties: '
  print "adminUrl=localhost:7101"
  print "adminUser=weblogic"
  print "adminPwd=welcome1"
#
#
def main():
  try:
    #
    # Section 1: Base Domain + Admin Server
    print (lineSeperator)
    print ('Enroll '+soaDomainName+' for NodeManager')
    print('\nConnect to AdminServer ')
    print (lineSeperator)
    adminURL=adminListenAddress+':'+adminListenPort
    connect(adminUser, adminPwd, adminURL)
    #
    print('\nPerform nmEnroll')
    print (lineSeperator)
    #
    nmEnroll(soaDomainHome, nodeManagerHome)
    #
    print ('\nFinished')
    #
    print('\nExiting...')
    exit()
  except NameError, e:
    print 'Apparently properties not set.'
    print "Please check the property: ", sys.exc_info()[0], sys.exc_info()[1]
    usage()
  except:
    apply(traceback.print_exception, sys.exc_info())
    stopEdit('y')
    exit(exitcode=1)
#call main()
main()
exit()

It can be called with a shell script (enrollDomain.sh) like:
#!/bin/bash
. fmw12c_env.sh
echo
echo Enroll domain
wlst.sh enrollDomain.py -loadProperties darlin-vce-db-osb.properties

I also adapted the function 'createUnixMachine' in the createSoaBpmDomain.py script:
#
# Create a Unix Machine
def createUnixMachine(serverMachine,serverAddress, serverPort, nmType):
  print('\nCreate machine '+serverMachine+' with type UnixMachine')
  print (lineSeperator)
  cd('/')
  create(serverMachine,'UnixMachine')
  cd('UnixMachine/'+serverMachine)
  create(serverMachine,'NodeManager')
  cd('NodeManager/'+serverMachine)
  set('ListenAddress',serverAddress)
  set('ListenPort',int(serverPort))
  set('NMType',nmType)
This allows for the non-default setting of the nodemanager to another listen port and nodemanager type, based on the following properties in the property file:
#
# Server Settings
nmType=Plain
server1Machine=darlin-vce-db
server1Address=darlin-vce-db
server1Port=5555
server2Enabled=false
server2Machine=darlin-vce-db
server2Address=darlin-vce-db2
server2Port=5555
An example of the property file can be found in the earlier article: automatic generation of a SOA/OSB domain.

Logging

In the example property file in my scripts you can set a location for the logging:
# Logs
logsHome=/u01/app/work/logs

If this is a absolute path, 'nothing is on the hand'. Your domain should start correclty. But if you choose to use a relative path, like 'logs', then starting the AdminServer might succeed (in my case) but starting the managed servers fail with a 'No such file or directory' message:
wls:/nm/osb_domain> nmStart('Adminserver')
 
Starting server Adminserver ...
 
Traceback (innermost last):
 
  File "<console", line 1, in ?
 
  File "<iostream", line 188, in nmStart
 
  File "<iostream>", line 553, in raiseWLSTException
 
WLSTException: Error occurred while performing nmStart : Error Starting server Adminserver : Received error message from Node Manager Server: [Server start command for WebLogic server 'Adminserver' failed due to: [No such file or directory]. Please check Node Manager log and/or server 'Adminserver' log for detailed information.]. Please check Node Manager log for details.
 
Use dumpStack() to view the full stacktrace :
 
wls:/nm/osb_domain

Now it turns out that this is caused by the JavaArgs that are generated and set in the script. I found that if you set JavaArgs you need to set redirects for weblogic.Stdout and weblogic.Stderr, like:
'-XX:PermSize=256m -XX:MaxPermSize=512m -Xms1024m -Xmx1532m -Dweblogic.Stdout='+logsHome+'AdminServer.out -Dweblogic.Stderr='+logsHome+'AdminServer_err.out'

Where logFolder should be an absolute path. This has to do with the context in which the nodemanager is starting the server. From that context the relative reference apparently does not evaluate to the proper location.
You can however, leave the Java args empty. So I changed my scripts to not use the getServerJavaArgs function, anymore, but get them from the property file. I replaced the xxxJavaArgsBase  with xxxJavaArgs variables. And left them empty.

The configurator doesn't set the JavaArgs, it leaves them over to the setDomain.sh/.cmd and setStartupEnv.sh/.cmd. If you do so, you can use a relative path, and the servers will start properly.

ServiceBus 12c: Logging

As a developer you probably 'log-a-lot' in OSB. (Funny term, perfectly to mock people that have the tendency to excessively add log activities/statements to their code. And hey, if you're being mocked like this: I'm happy to join you, let's make it a 'Geuzennaam').

So as a log-a-lot, I was questioned by a OSB developer this week on a OSB11g->SB12c upgrade that I support, that his logs weren't visible in the server-logs in de 12cR2 SOAQuickStart Integrated Weblogic.

We reviewed the logging settings of the server, all set on Debug.

But it turns out that in the SB Logging configuration in EM all is set to Warning (Inherited).

To check it out, go to http://localhost:7101/em (on a default configured SOA QuickStart Integrated Weblogic domain).

Click on the Target Navigator and Navigate to soa-infra->Service Bus.

Then in the Service Bus menu, navigate to Logs ->  Log Configuration:

Then you need the Log-Levels tab:
Here you see that you can set another level on different SB subsystems. It can be interesting to check out some of those. For instance, I think it was oracle.osb.debug.instancetracking (but I'm not sure, I checked several of them) enable the logging of message contents and variable changes. You see that all the log-level settings are set to Warning, inherited from the level above.

But the one that is of interest for the pipeline-logging is oracle.osb.logging.pipeline:
Set that one on Trace. I used TRACE:16 (FINER). But it turns out that the levels here are different from those in the pipeline log activity and that of those of  the WebLogic Server logs. I haven't got a mapping at hand, but this one let's Debug messages through.

Hit the apply button top right:

This should enable the logging of the Pipeline.

Tuesday 21 June 2016

Start problems with Nodemanager

Since my previous long running assignment, I'm involved in a few OSB 11g to 12c upgrade trajects, where I have been working on automatic installs. Hence, my articles about automatic installs, patching and domain configuration.

When I create a new domain, 12cR2 (12.2.1), using my scripts, I'm not able to use the nodemanager of the domain to start the servers. Actually, I can't connect to it. I get it running alright, but connecting to it fails with a message like:
WLSTException: Error occured while performing nmConnect : Cannot connect to Node Manager. : Access to domain 'osb_domain' for user 'weblogic' denied.

When you google the standard solution is to change the nodemanager password on the security tab of the domain in the console.Like in this article. Actually a very good suggestion, but it did not work for me. It turns out that apparently performing an nmEnroll() did the job in that case. What you need to do is:
  • Start the admin server using the startWeblogic.sh script in the domain root.
  • Verify and correct the nodemanager settings under Environment->Machines-><your machine>, ensuring it is inline with the file nodemanager.properties in {domainHome}/nodemanager.
  • Start wlst.sh
  • Connect to the adminserver using: connect({adminuser}, {adminpassword} ,'{adminHost}:{adminPort}')
  • Perform: nmEnroll({domainHome}, {domainHome}/nodemanager)
Here I assume the use of a per-domain nodemanager, where {domainHome}/nodemanager is the nodemanager home within the domain home.

Then I was able to connect to the nodemanager, start the AdminServer and then the OSB Server.


At my customer, they have been struggling in configuring a 'standalone' nodemanager, as they did in the 11g situation. Nodemanager can be started, and connected to. But doning an nmStart of the admin server got:

wls:/nm/osb_domain> nmStart('Adminserver')

Starting server Adminserver ...

Traceback (innermost last):

  File "<console>", line 1, in ?

  File "<iostream>", line 188, in nmStart

  File "<iostream>", line 553, in raiseWLSTException

WLSTException: Error occurred while performing nmStart : Error Starting server Adminserver : Received error message from Node Manager Server: [Server start command for WebLogic server 'Adminserver' failed due to: [No such file or directory]. Please check Node Manager log and/or server 'Adminserver' log for detailed information.]. Please check Node Manager log for details.

Use dumpStack() to view the full stacktrace :

wls:/nm/osb_domain

This is also in 12cR2 (12.2.1), with a domain created with the same script. Sharp eyes may notice that the adminserver name is not default: it has a lowercase 's' in stead of a uppercase. They've been fiddling a round with naming of the admin server. What we finally did was to keep the un-default naming, but cleansed the server folder by removing the data, cache and tmp folder. We also removed the logs folder to be able to see if new logs were made from starting from the nodemanager. We configured the per-domain nodemanager and then we did the same as above, performing an nmEnroll() against the domain-nodemanager. After that the 'Adminserver' was startable.

Conclusion

I hardly ever had the need to use nmEnroll(), not with a new domain and in 11g at least not even using a seperate nodemanager. From colleagues I did not hear the need to use it in 12c. So why did I need it to solve the sketched problems? I haven't sorted that out, I hope I once get a finger around it. For the moment, take advantage of these experiences.

Saturday 18 June 2016

Object Oriented Pl/Sql

Years ago, in my Oracle years I wrote an article on Oracle (Object) Types, and how those make Pl/Sql so much more powerfull. It was in Dutch, since I wrote it for our monthly internal consulting magazine called 'Snapshot'. Since it was in Dutch and I regularly refer to it on our blog or in questions on forums, I wanted to rewrite it for years. So let's go. Oracle Types are introduced in the Oracle 8/8i era. And enabled me to build stuff that were not possible using regular Pl/Sql.

In Oracle 8/8i the implementation lacked constructors, but it was already very powerful. From Oracle 9i the possibilities were extended a lot and brought it to where it still is, I think. So everything that I post here is possible from Oracle 9i and later.

And you may ask: why bother an extension in Pl/Sql dated about 10 tot 15 years ago? And why if I'm into SOA Suite and/or OSB? Well, I really think that Pl/Sql in combination with Object Types is the best tool at hand for creating API's to Oracle Database applications. And the DB Adapter's capabilities of calling Pl/Sql functions with Object Type parameters is very strong. Together it is the best integration pattern for the Oracle Database. Even for data retrieval, it's much stronger than stand alone queries or views.

Do these Object Types make Pl/Sql an object oriented language? I'm not going to discuss that in extend. I think it is much more like Turbo Pascal 5.5 was OO: I think it's more a 3GL with Object-extensions. So if you're an OO-purist: you're right upfront, as far as I'm concerned. But object-types make the life of a Pl/Sql programmer a lot more fun. And I feel that still after all those years the capabilities aren't utilized as much as could be.

 So let's dive in to it. We start at the basics.

A type with a constructor

 A type with a constructor and some methods can be created as follows:

create or replace type car_car_t as object
(
  -- Attributes
     license   varchar2(10)                     
  ,  category  number(1)     
  ,  year      number(4)     
  ,  brand     varchar2(20)  
  ,  model     varchar2(30)  
  ,  city      varchar2(30)
  ,  country   varchar2(30)
  -- Member functions and procedures
  , constructor function car_car_t(p_license in varchar2)
    return self as result
  , member function daily_rate(p_date date) 
    return number
  , member procedure print
)
/

I don't intent to give a college on object orientation here,  but if you look at the type specification it is immediatly clear that an Oracle Type is a kind of record-type on it's own, but that besides attribute it also contains executable additions: methods.

Methods are functies and/or procedures that execute on the attributes of the type. Within the method you can see the attributes as 'global' package variables.

As said, a very convenient addition in the Types Implementation is the possibility to define your own  constructors. They're declared as:
  , constructor function car_car_t(p_license in varchar2)
    return self as result

A constructor starts with the keyword constructor and is always a function that returns the 'self' object as a result. Also, the constructor is always named the same as the type itself. Implicitly there's always a constructor with all attribute as a parameter. This was already the case in Oracle 8, but from Oracle 9i/10g onwards this is still delivered for free. But besides the default constructor you can define several of your own. br /> This enables you to instantiate an object based on a primary key value, for example. Based on that key you can do a select into the attributes from a particular table. Or instantiate a type based on the read of a file. Or parameter-less so that you can just instantiate a dummy object that can be assigned values in a process. This is especially convenient if you have a very large object, where not all the attributes are mandatory.
Often I add a print method or a to_xml or to_string method. This enables you to print all the attributes or return an XML with them, including the call of the same method in child objects. Child objects are attributes based on other types or collections.
The implementation of the methods are in the Type Body:
create or replace type body car_car_t is
  
  -- Member procedures and functions
  constructor function car_car_t(p_license in varchar2)
    return self as result
  is
  begin
    select license
    ,      category
    ,      year
    ,      brand
    ,      model
    ,      city
    ,      country
    into   self.license
    ,      self.category
    ,      self.year
    ,      self.brand
    ,      self.model
    ,      self.city
    ,      self.country
    from cars
    where license = p_license;
    return;
  end;
  member function daily_rate(p_date date)
  return number
  is
    l_rate number;
    cursor c_cae( b_license in varchar2
                , b_date    in date)
    is select cae.dailyrate
       from   carsavailable cae
       where  b_date between cae.date_from and nvl(cae.date_to, b_date)
       and    cae.car_license = b_license
       order by cae.date_from;
    r_cae c_cae%rowtype;
  begin
    open c_cae( b_license => self.license
              , b_date    => p_date);
    fetch c_cae into r_cae;
    close c_cae;
    l_rate := r_cae.dailyrate;
    return l_rate;
  end;
  member procedure print
  is
    l_daily_rate number;
  begin
    dbms_output.put_line( 'License   : '||self.license);
    dbms_output.put_line( 'Category  : '||self.category);
    dbms_output.put_line( 'Year      : '||self.year);
    dbms_output.put_line( 'Brand     : '||self.brand);
    dbms_output.put_line( 'Model     : '||self.model);
    dbms_output.put_line( 'City      : '||self.city);
    dbms_output.put_line( 'Country   : '||self.country);
    l_daily_rate := daily_rate(p_date => sysdate);
    if l_daily_rate is not null
    then
      dbms_output.put_line('Daily Rate: '||l_daily_rate);
    else
      dbms_output.put_line('No cars available');
    end if;
  end;
  
end;
/

Here you see that I used a primary key based constructor to do a select from the cars table into the attributes. And do a simple return. I do not have to specify what I want to return, since it somehow does return 'itself'. That is: an instance of the type. So the return variable is more or less implicit.

The print method enables me to test the object easily after the instantiation:
declare 
  -- Local variables here
  l_car car_car_t;
begin
  -- Test statements here
  l_car := car_car_t(:license);
  l_car.print;
end;

Collections 

An object don't come alone very often. Same counts for Object-instances. We talk with the database so in most cases we have more than one instance of an entity

Een a set of object-instances is called a collection. And is defined as:
create or replace type car_cars_t as table of car_car_t;


So a collection is actually a table of objects  of a certain type. Oracle is even able to query on such a collection, but I'll elaborate on that later.

Note, by the way, that there now is a reference to, or put otherwise, a dependency to the particular object type. This means that the object-specification of in this case 'car_car_t' can't be changed anymore, without dropping all the references to it. This may become quite inconvenient when changing a large hierarchy of object types. So you'd better create an install script right away, that can recreate (drop and create) the complete tree.

The 'body', the source code, can be recompiled. This is important, because the specification defines the  structure of the object (the class) and other objects are to depended on this interface. Maybe Oracle should define an interface type, so this can be made a bit more loosly coupled.

This counts especially when it comes to table definitions (in the database) where you can add an object-type based column. After all, a physical table can't become invalid. What should become of the data in that case? That could become 'undefined'.  For the rest, you can see Collections an ordanary Pl/Sql table, comparable to an "index by binary_integer"-table. But  with the difference that it is an (stand-alone) object in itself, containing other objects. This means that to be able to use a Collection it has to be instantiated. This can be done implicitly by means of a query:
select cast(multiset(
select license
,      category
,      year
,      brand
,      model
,      city
,      country 
from cars
) as car_cars_t)
from dual

What this actually does is that the result set of the select on the table cars is being redefined as a Collection. The Multiset-function denotes that what is returned is actually a data-set of zero or more rows. The Cast-function is used to denote as what datatype/objecttype the multiset should be considered. You could say that over the result set a collection layer is layed. I haven't been able to test it, but I am curious about the performance effects. What would be the difference between this query and for example a for-cursor-loop? Now it is seasoned with a collection-sauce you can handle this resultset as were it a Pl/Sql-table in memory after all.


Of course you can instantiate and fill the collecation more explicitly like:
declare 
  l_cars car_cars_t;
begin
  l_cars := car_cars_t();
  for r_car in (select license from cars)
  loop
    l_cars.extend;
    l_cars(l_cars.count) := car_car_t(r_car.license);
  end loop;
  if l_cars.count > 0
  then
    for l_idx in l_cars.first..l_cars.last
    loop
      dbms_output.put_line('Car '||l_idx||':');
      l_cars(l_idx).print;
    end loop;
  end if;  
end;

In this case in the collection is instantiated in the first line. Then a for loop is started based on the select of the primary key on the cars table, which is the license column. Then in the loop, on each iteration the collection is extended. And then a new instance of car_car_t, using the primary key constructor with the car's license is assigned to the last row of the collection, denoted with the implicit count attribute of the collection.
In the second loop an example is given, which shows how easily you can traverse the collection and print each row-object.

Object-Functions and Views The creation and propagation of a collection can also be put in a function of course:
create or replace function get_cars 
return car_cars_t is
  l_cars car_cars_t;
begin
  select cast(multiset(
  select license
  ,      category
  ,      year
  ,      brand
  ,      model
  ,      city
  ,      country
  from cars
  ) as car_cars_t)
  into l_cars
  from dual;
  return(l_cars);
exception
  when no_data_found then
    l_cars := car_cars_t();
    return l_cars;
end get_cars;
/

This function get_cars has no input parameters, you could restrict the query based on model or year for instance. But it returns the car_cars_t collection. If there are no cars available this query raises a no_data_found exception, since it does a select-into. But since it happens in a function, the nice thing is that you can catch the exception and just return an empty collection.

But the fun part is that you can use the result of that function as the source of a query. So, you see in the function that you can lay a collection-sause over a result-set, but the other way around is also possible: a collection can be queried:
select car.license
,      car.category
,      car.year
,      car.brand
,      car.model
,      car.city
,      car.country
,      car.daily_rate(sysdate) daily_rate
from
table(get_cars) car

The trick is in the table function. That directs Oracle to consider the outcome of the function as a result set. The example also shows that the methods are also available. But of course only if it's a function. By the way, in this example the attributes and the method-results are simple scalair datatypes, but they also could be types or collection. And those are available in the query. The attributes of object-attributes can be referenced in the query with the dot-notiation ('.'). In other words: hierarchical deeper attributes can be fetched as a column value and returned this way.

In this case we use a function as the base for the query. In that case it is also possible to create a view on top of it. As long as the function and the object-types that are returned  are 'visible' for the user/the schema that is owner ovthe view and/or uses the view.

But to stretch it some more: not only the result of a function can be used as the base of a function. Also a local variable or package-variable can be supplied as the source of a query:
declare
  l_cars car_cars_t;
  l_rate number;
begin
  l_cars := get_cars;
  select car.daily_rate(sysdate - 365) daily_rate
  into l_rate
  from table(l_cars) car
  where license = '79-JF-VP';
  dbms_output.put_line( l_rate);
end;
Isn't this a lot easier than to traverse a pl/sql-tabel in search for that one particular row?
Now, you could think: isn't this a full-table scan then? Yes indeed. But this fulltable scan is done completely in memory and therefor very fast. And let's be honest: who created a pl/sql-table of more than a gigabyte? Although using the examples above this can be done quite easily. So a bit of  performance-aware programming is recommended.

Pipelining

In the previous paragraph I already mentioned performance. With the collection-function-methodevan above you could program your own 'External Tables' in Oracle 8i already (External Tables were introduced in 9i). So you could, for example, read a file in a Pl/Sql-function using UTL_File and process it into a collection and return this.

Then you could create a view around it with the table-functie and do a query on a file! Impressive, huh? A very important disadvantage of this method is that the function is executed as a logical/functional unite completely. So the complete file is read, the complete collection is built and returned to the caller as a whole. That means that doing a sleect on that function, the function is executed completely, before you'll get your result. This is especially inconvenient when the after-processing on the result of the function is time-expensive as well. This is why in Oracle 9i pipelining is introduced.

A pipelined function is functionally identical to the collection-returning-function as I described above. The most important difference is that is denoted that is about pipelined function (duh!), but more-over that the in-between=results are piped (sorry, in Dutch this is funny, but I did not made up the term) and thus returned as soon as they become available.

This looks like:
create or replace function get_cars_piped(p_where in varchar2 default null)
return car_cars_t
pipelined
is
  l_car car_car_t;
  type t_car is record
  ( license cars.license%type);
  type t_cars_cursor  is ref cursor;
  c_car t_cars_cursor;
  r_car t_car;
  l_query varchar2(32767);
begin
  l_query := 'Select license from cars '||p_where;
  open c_car for l_query;
  fetch c_car into r_car;
  while c_car%found
  loop
    l_car := car_car_t(p_license => r_car.license);
    pipe row(l_car);
    fetch c_car into r_car;
  end loop;
  close c_car;
  return;
end get_cars_piped;

So you see indeed the keyword 'pipelined' in the specification of the function, and after that in the loop that each separate object using the 'pipe row' statement is returned. You could say that 'pipe row' is like an intermediate return. Besides that you get in this function, completely for free and on the house, an example of the use of a ref-cursor. With this it is possible to build up a flexibele cursor of which you can adapt the query. You can call this function as follows:
select *
from table(get_cars_piped('where license != ''15-DF-HJ'''))

I found that is is not possible to call this function in a pl/sql-block directly. If you think about it, it seems logical What happens in the statemnt is that the sql-engine calls the pl/sql-function and receives each row directly and is able to process it. This way it is possible to execute the function and process the result simultaneously. Pl/Sql in it self does not support threads or pipe-lines. Pl/Sql expects the result of a functie-call as a whole and can advance with processing only if the function is completely done.

Object Views

Now you have seen how to create a Collection sauce over a resultset and how a Collection can be queried using Select-statements. An other important addition Oracle 9i are the so called  object views (I say important, but I haven't seen them much out in the open). Object views are views that can return object-instances. This contrast to regular views that return rows with columns.
An object view is defined as follows:
create or replace view car_ov_cars
of car_car_t
with object oid (license)
as
select license
,      category
,      year
,      brand
,      model
,      city
,      country
from   cars

Typical to an object view is that you denote what the object-type is where the view is based upon and what the object-identifier (oid) is. That is actually the attribute or set of attributes that count as a  primary-key of the object.

You could query this view as a regular view, but the strength is in the ability to fetch a row in the form of an object. This is done using the function 'value':
declare
  l_car car_car_t;
begin
  select value(t)
  into l_car
  from car_ov_cars t
  where license = '79-JF-VP';
  l_car.print;
end;

This delivers you an object-instantie from the view without any hassle. Very handy if you're using the objects extensively.

References

When you have a extensive object model, than you might run into objects with one or more collections  as attributes. Those collections can also have multiple instances of other object types. This can become quite memory intensive. Besides that, you can run into the need to implement circulaire-references. For example a department with a manager is an employee him/her self and directs one or more other empoyees. It could be that you wanted to model that as an employee with an attribute typed as a collection type on the employee-type. It could be convenient to have a more louse coupling  between objects.

For that a concept of References is called into live. In fact, a reference is nothing more than a pointer to another object-instance. And that uses less memory than a new object instance. You could refer to an object-instance in a object-table of or an object-view. And than the object-identifier from the previous paragraph comes in handy.

An collection of references is defined as:
create or replace type car_cars_ref_t as table of ref car_car_t;

You can propagate this with the make_ref function:
declare
 l_cars car_cars_ref_t;
 l_car  car_car_t;
begin
  -- Build a collection with references
  select cast(multiset(select make_ref( car_ov_cars
                                      , cae.car_license
                                      )
                       from carsavailable cae) as car_cars_ref_t)
  into l_cars
  from dual;
  -- Process the collection
  if l_cars.count > 0
  then
    for l_idx in l_cars.first..l_cars.last
    loop
      dbms_output.put_line( 'Car '||l_idx||': ');
      -- Get object-value based on the reference 
      select deref(l_cars(l_idx))  
      into l_car
      from dual;
      -- Druk object af
      l_car.print;
    end loop;
  end if;
end;

Here you see that the make_ref needs a reference to an object-view and the particular object identifier. The underlying query than delivers a reference to the objects that need to be processed. That query can be different to the query of the object-view.

What it actually means is that you first determine which  objects are to be taken into account. For those objects you determine a reference/pointer based on the  object-view. And than you can get the actual instance using the reference in a later stage.

The latter is done using the deref-function. This deref-function expects a reference and delivers the actual object instance. The deref is only available in a SQL-function taste, by the way. You cannot use it in Pl/Sql directly. Under water a  'select deref()'-query is translated to a  select on the object-view.

It is important then, to design your object model and object view in a way that the actual query on that object view is indexed properly. The experience learns that it can be quite difficult to determine why the optimiser does or doesn't use the index with derefs.  In that the deref is a nasty abstraction.

The ref that you see in the ref-collection declaration, you can use in the declaration of attributes as well. When you want to use an object as an attribute in another object, for instance an object car in the object garage, than you can use the keyword ref  to denote that you don't want the object itself but a reference:
create or replace type car_garage_t as object
(
  car ref car_car_t
)

Then there is also a ref function that creates references to seperate objects:
select ref(car) reference
,      license
from car_ov_cars car

This function is actually a counterpart of the value-function.
The difference between the functions ref and make_ref is actually that 'ref'  gets the object as a parameter for which a reference must be determined. Make_ref, however is based on an object-view or object-table and determince the reference based upon the primary-key or object-id in the object-view or -table.

The ref-function is used when you ned to create a reference to an object that is a direct result of a query on the object-view. But if you want to determine the primary keys of objects you want to process,  based upon a query on other tabels and/or views than make_ref comes in handy. Because then you deliver the primary-keys of the objects to process separately and  then make_ref uses the object-view and the primary-key values to determine the references.

MAP and Order methods

Now sometimes you need to order a objects. Which one is bigger or smaller and how do I sort them? Obviously this is important when comparing objects but also when querying object-views and object-tables.
For the comparison of objects you can create a map-method:
map member function car_size
return number
is
begin
  return 1000; -- or a calculation of the content of the car, or the prize or fuel-consumption
end; 

In the implementation you can do a calculation on the attributes of the object. The result needs to be of a scalair datatype (number, date, varchar2) and 'normative' for the object with regards to other objects of the same  object-type. The map-method can then be used by Oracle to do comparisons like  l_car1 > l_car2, and comparisons that are implied in  select-clausules as: DISTINCT, GROUP BY, and ORDER BY. Imagine how compact your code can be if you implement methods like these.

You can also make use of an Order method:
order member function car_order(p_car car_car_t)
return number
is
  l_order   number := 0;
  c_smaller constant number := -1;
  c_larger  constant number := 1;
begin
  if licence < p_car.license
  then
    l_order := c_smaller;
  elsif  licence > p_car.license
  then
    l_order := c_larger;
  end if;
  return l_order;
end;

The difference with the map-method is that the map-method returns a value that only has meaning for the object it self. The implicit parameter is only the 'self'-object. Oracle determines the results of the map-method for the two objects to be compared and compares the two results. With the order-method Oracle will provide one object as a parameter to the order-method of the other object. Therefor the order method always needs an extra parameter besides the implicit self-parameter. In the function's implementation you code the comparison between those two objects yourself. And that can be a lot more complex then above. Then you provide a negative value if the self-object is smaller then the provided object and a positive value if the self-object turns out larger. A value of 0 denotes an equalness of the two objects.  The Order-method is used with l_car1 > l_car2 comparisons and always need to have a numeral return datatype.

An object can have only one map-method and one order-method.

Conclusion

Maybe it dazzles you by now. But if you got through to here, then I'm impress. It might seem like a bit boring stuf. And it might seem quite devious if you start with it. Most functionality you need to build can be done in the Oracle 7 way. But certain solution can become a lot more powerful if you do it using object-types. I use them thankfully for years now. But then I am someone who likes to solve a similar problem in a different way the next time it comes around.

Because of object-types Pl/Sql becomes a lot more powerful and it provides you with more handles to solve some nasty performance-problems. Or pieces of functionality that really aren't solvabale int the Oracle 7 way.

And as said in the intro: Oracle Types are really the a game-saver for SOA Suite and Service Bus integrations with the Database Adapter. Because using a hierarchy of objects you'll be able to fetch a complete piece of database with one database call. I even created a Type-Generation-framework (I called it Darwin-Hibernate) that can create types based on the datamodel in the database. It then creates constructors and collection-objects over foreign-keys that allows you to instantiate a complete structure based on the constructor of the top-level object. For instance a Patient with all it's medical records, addresses, etc.

Al the examples already work with Oracle 9i. But under 10g, 11g, 12c it will run a lot smother and faster because of the performance optimalisations of the Pl/Sql-engine.(Oracle 9i was not quite a performance topper).

This wasn't a story about Object Oriented Pl/Sql, actually. I didn't talk about super and sub-types. you can read about that in Chapter 12 of the Pl/Sql User's Guide enReference van Oracle 10g (I really ran into that page when Googling on it...). Or this page in 11g.
But I wanted you to get started with Object Types, and show you what you can do with it and how powerful Pl/Sql has become with it.

For some more advanced stuff you can read my earlier article about type inheritance, regarding EBS, but interesting enough for non-EBS developers. And another one. And yet another one.

Have fun with Pl/Sql (you might think by now that I really feel Pl/Sql needs this uplift), because I think with Object Types Pl/Sql is really fun. The scripts and datamodel for the examples can be found here.

Wednesday 15 June 2016

Servicebus Overview diagrams in 12cR2 not opened for upgraded services

In ServiceBus 12c you get a 'composite'-alike service overview for your project. It shows you how the proxy services (like Exposed Services in SOASuite) via pipelines are 'wired'to business services (like Referenced Services). This is nice!

If you upgrade a project from 11g or 12cR1 (12.1.3) to 12cR2 (12.2.1) this fails. Initially you might see a (correct) diagram, but after restarting JDeveloper this is empty. You'll get a Class cast exception:
java.lang.ClassCastException: oracle.tip.tools.ide.fabric.addin.CompositeNode cannot be cast to oracle.sb.tooling.ide.sca.internal.sca.SbCompositeNode
at
oracle.sb.tooling.ide.sca.internal.sync.CompositeEditorListener.editorOpened(CompositeEditorListener.java:72)

Also after opening a earlier upgraded project, OSB diagrams fail to open and instead generate this java.lang.ClassCastException.
I search on support.oracle.com  and found this document: 'Unable to open OSB Diagrams upgraded from 12.1.3 to 12.2.1 (Doc ID 2124208.1)'
It refers to the patch:
  • 22226040: java.lang.NullPointer for XQuery File ver 1.0 in JDEV 12.2.1 OSB Proj
 Apply this patch (for instance via my patch script in my previous article). The document suggests to backup and remove the user-data folder of your JDeveloper QuickStart installation. I found that not necessary. But you do need to re-import the projects from the earlier versions. I would empty the .data folders on application and project levels before starting JDeveloper again. Then delete the upgraded projects and reimport them again. Now after restarting JDeveloper the overviews should remain.

Automatic Patching of SOA/BPM QuickStarts

Earlier I wrote how to automatically install the SOA/BPM QuickStarts. Actually, I'm quite busy with doing automatic/scripted installs for SOA/BPM Suite and OSB, as you might have read.

At my current customer we encountered that in the last months there are many one-off-patches released on support.oracle.com. We selected a pretty large bunch of patches and apply them one by one is a tedious job. But the thing is with these automatic installs that you want to have a uniform installation for each developer so each developer should have the same patches installed, in the same location. And you probably want to be able to quickly do a re-install to a uniform setup.

So I figured out how to do a silent install of the patches and to do this in a loop.

Out of the selected patches I found 5 catechories:
  • 001: JDeveloper patches, where only a few we found possibly applicable for the SOA/BPM QuickStarts
  • 002: ServiceBus related patches
  • 003: SOA Suite merged patches
  • 004: SOA Suite related other patches
  • 005: BPM Suite merged patches
  • 006: BPM Suite related patches
So I divided the patches in those 5 folders ('001', '002', ... , '005'). And I numbered them in 3 digit folders, since my scripting needed to figure out the patch number from the path name. Each patch is a zip with a sub-folder with only the patch number (without the 'p'). So the path-lengths needed to be equal (so not 'jdev' and 'soa' or 'osb').

I have one main script called 'installQSPatches.bat' that loops over the files in each sub-folder:
@echo off
rem check SOA12.2 QS
setlocal
set FMW_HOME=C:\oracle\JDeveloper\12210_BPMQS
set ORACLE_HOME=%FMW_HOME%
set SOA_PATCH_SOURCE=SOA
set SOA_PATCH_HOME=%FMW_HOME%\Opatch\patches
set CUR_DIR=%~dp0
echo Current Dir: %CUR_DIR%
if exist "%FMW_HOME%" goto :SOAQS_HOME_EXISTS
echo %FMW_HOME% not installed yet! Install first!
goto :DONE
:SOAQS_HOME_EXISTS
echo %FMW_HOME% exists, install Patches
echo ____________________________________________________
call %FMW_HOME%\wlserver\server\bin\setWLSEnv.cmd
echo ____________________________________________________
:JDEV_PATCHES
echo -
echo JDeveloper Patches
echo ____________________________________________________
for %%f in (001\*.zip) do (
  echo %%f
  call applyPatch %%f 
)
:SB_PATCHES
echo -
echo ServiceBus Patches
echo ____________________________________________________
for %%f in (002\*.zip) do (
  echo %%f
  call applyPatch %%f 
)
:SOA_MERGE_PATCHES
echo -
echo SOA Suite Merged Patches
echo ____________________________________________________
for %%f in (003\*.zip) do (
  echo %%f
  call applyPatch %%f 
)
:SOA_PATCHES
echo -
echo SOA Suite Patches
echo ____________________________________________________
for %%f in (004\*.zip) do (
  echo %%f
  call applyPatch %%f 
)
:BPM_MERGE_PATCHES
echo -
echo BPM Suite Merged Patches
echo ____________________________________________________
for %%f in (005\*.zip) do (
  echo %%f
  call applyPatch %%f 
)
:BPM_PATCHES
echo -
echo BPM Suite Patches
echo ____________________________________________________
for %%f in (006\*.zip) do (
  echo %%f
  call applyPatch %%f 
)
:DONE
echo Done installing patches
endlocal
You need to shutdown JDeveloper and IntegratedWeblogic before starting this script.
And it needs to be started in an elevated (as Administrator) command window.

For each patch it calls the applyPatch.bat script:
set PATCH=%1
set PATCH_NR=%PATCH:~5,8%
echo ____________________________________________________
echo Check Patch %PATCH% for patch nr %PATCH_NR%
if exist "%FMW_HOME%\Opatch\patches\%PATCH_NR%" goto :PATCH_EXISTS
echo "%FMW_HOME%\Opatch\patches\%PATCH_NR%" does not exist.
set SOA_PATCH_HOME=%FMW_HOME%\Opatch\patches
rem set SOA_PATCH_HOME=c:\temp\patches
echo .. Unzip %SOA_PATCH_SOURCE%\%PATCH% to %SOA_PATCH_HOME%
call ant -f ant-zip.xml unzip -Dzip-file=%PATCH% -Dunzip-destination=%SOA_PATCH_HOME%
cd %ORACLE_HOME%\Opatch\patches\%PATCH_NR%
echo  .. Apply %ORACLE_HOME%\Opatch\patches\%PATCH_NR%
call %ORACLE_HOME%\Opatch\opatch apply -silent
cd %CUR_DIR%
goto :DONE
:PATCH_EXISTS
echo Patch %PATCH_NR% already exits!
:DONE
echo Done for patch %PATCH_NR%
echo ____________________________________________________

This one figures out what the patchnumber (%PATCH_NR%) is based on the patch-file-name, and if that patch already exists in the %FMW_HOME%/Opatch/patches folder. If not it will unzip the patch file to that folder, resulting in a sub-folder named with the patch number. Then it will apply the patch using Opatch in silent mode.
For the unzip, it uses a simple ANT build file, since the Windows Command screen does not support a commandline-unzip (as far as I could find). The ANT script is as follows:
<?xml version="1.0" encoding="UTF-8"?>
<project name="zip" default="zip" basedir=".">

 <target name="zip">
  <zip destfile="${zip-file}.zip" basedir="${folder-to-zip}" excludes="dont*.*" />
 </target>

 <target name="unzip">
  <unzip src="${zip-file}" dest="${unzip-destination}" />
 </target>

</project>

When I finished this I thought I should convert it to a complete ANT script. But maybe later.

The selected patches for 12.2.1:
The selected JDeveloper patches (sub-folder 001) were:
  • 22283405    JDEV 12.2.1 - NULLPOINTEREXCEPTION ENCOUNTERED (Patch)
  • 23266774    VALIDATION ERRORS WHEN CLICKING ON MANDATORY FIELDS IN JDEV12.2.1 (Patch)
  • 22463346    NOT STRESS SOA: MULTIPLE ERROR MESSAGES OF DEFINITIONMANAGER.LOCKINGLOGGER (Patch)
  • 21890657    CREATE REST THROWS ERROR 500 FOR REFERENCED ENTITY (Patch)
These are not all the possible JDeveloper patches and even may not apply to developing SOA/BPM or SB processes or services.

The selected Service Bus patches (sub-folder 002) were:
  • 23223332 Need to provide a holistic solution in main line for Bug 22887808
  • 21824551 NPE while trying to read OWSM keystore
  • 21168191 UnsupportedOperationException from ServiceAccountRuntimeCache
  • 23184618 Http Transport throws NullPointerException
  • 21827583 Deploying existing .sbar with Maven
  • 22738111 OSB Java Call out method's not visible from JDeveloper
  • 20119834 need to trim headers size if size exceeds 998 characters.
  • 22358699 OSB12C fn-bea:inlinedXML does not work properly
  • 22374613 In 12.2.1 the OSB Projet pom files still says 12.1.3
  • 20196110 JDev OSB Extension has missed export Split-Join and Proxy Flow as png
  • 22187224 OSB 12.2.1 - MessageID changes between request and response messages
  • 22392646 Maven could not be used in Jdev in 12.2.1.0.0
  • 22602059 12C: OSB pipeline based on XML - $body structure incomplete - missing node
  • 22276364 12C: OSB pipeline based on XML - $body structure unavailable
  • 21659900 OSB removes WSA headers on outbound request 
 The selected SOA Suite merged patches (sub-folder 003) were:
  • 23543517 MERGE REQUEST ON TOP OF 12.2.1.0.0 FOR BUGS 23527297 22875806 22995356 23062804
  • 23138916 MERGE REQUEST ON TOP OF 12.2.1.0.0 FOR BUGS 21549249 21572567
  • 23106839 MERGE REQUEST ON TOP OF 12.2.1.0.0 FOR BUGS 21826430 22912570
  • 23134140 Diagnostic Tracking Bug for Bug 23108573 v2
The selected SOA Suite regular patches (sub-folder 004) were:
  • 23056585 XQuery transformation is not showing all the types of XSD in the design view
  • 21904101 Error when running ValidateComposite to project with bpel calling HWF
  • 23205706 MALFORMEDURLEXCEPTION EXCEPTION ON JDEV 12.2.1.0.0 - BAM 12C IDE CONNECTION
  • 23193066 BPEL polls from UMS Adapter results in too large CONVERSATION_ID error
  • 21698320 SELECTING ELEMENT IN LARGE DOCUMENT TAKES FREEZES 2+MINUTES
  • 23108573 TrackingContextProperty causes ClassCastException in SOA 12c Spring Composite
  • 21925552 SOA Maven Plugin requires deploying sar to server when running mvn install
  • 23186275 JDeveloper 12.2.1 JMS Adapter not displaying Elements from the imported xsd file
  • 22337707 Naming conflict with EDN event subscribers from single Mediator component
  • 23052343 16.2.3 : WS JOB REQUEST ENDS UP IN ERROR STATE WHEN INVOKED VIA OTD URL
  • 22026475 JDEV : Adapter : Nullpointerexception while creating the BPEL Process
  • 22815366 Unable to use WSDL containing an unsupported Notification Operation portType
  • 22978098 Indicators configured on Response payload coming from a DB ADapter do not work
  • 21835972 SFTP FileListing not work as expected. Need similiar bug fix as in bug 21176154
  • 22648699 SOA Suite12.2.1-JCA files not getting updated with Configuration Plan values
  • 22300448 JDeveloper doesn't add a libraries file group to existing SOA deployment profile
  • 16548396 Can not emulate fault from the SOA Test UI
Of these the patches 21925552 conflicts with 21904101 and 23193066 conflicts with 23108573. So you should choose which one you want to apply.

For BPM (sub-folder 005) the following merged patches were selected:
  • 22571194 NPE in o.bpm.project.sca.loader.impl.ElementContainer:82
  • 23283093 NPE in o.bpm.project.sca.loader.impl.ElementContainer:198
For BPM (sub-folder 006) the following patches were selected:
  • 22191778 In the Business Architecture Modeling links section, buttons are missing
  • 21325503 12c compilation err-crm: oracle.bpm.services : extncontentpublicmodeleventaction
  • 22736087 BPM Workspace not refreshing the task list after Initiator task page is closed
  • 22018713 MeasurementPublisher has been optimized
  • Software 22217468 Refresh is not working properly in Impact Analysis Report - 500 error
  • 22018703 MIAuditLog has been optimized
  • 22690780 workspace Title & logo unchanged on loginpage after patch 22272135
  • 19536412 Case Service handles namespaces incorrectly in SOAP XML messages
  • 22348182 EDG RC3a :Task comments cannot be added
  • 22087208 When approving a task it immediately opens the next task in your tasklist
  • 22753983 12c e-manager authorization issues - List view
  • 22111729 BPM instance is not released after completing an HT ith the voting patern
  • 22581888 Configure global option to enable task details to appear as pop-up window in 12c
  • 23077279 Composer crash when creating Org Unit or Application system for human task
  • 23125361 REST hrefs for attachments containing spaces in their names do not work.
  • 23205514 Flexfields not properly displayed in parallel task with SDO
  • 23301503 weblogic user or Administrator group shouldn't be necessary to login to composer
  • 23491602 Studio generated project_properties.wsdl missing the correlation mapping
  • 21792613 NOT STRESS BPM REST:get WebFormService - no valid constructor error 
 From these the 22581888 conflicts with 22087208. And  23205514 conflicts with 22348182.

Download the applicable patches for your situation and put them in the appropriate folder. If you use the SOA QuickStart in stead of  BPM QuickStart, you should skip the BPM Related patches of course.
UPDATE september 26th, 2016: Since this is a Windows script: you need to run this on the same drive as the QuickStart installer. It does a change dir to the patch and then an 'Opatch apply'. But if it's not run on the same drive, the change dir succeeds, but it's on another drive so still not current.

Thursday 9 June 2016

Scripted Domain Creation for SOA/BPM, OSB 12.2.1

Recently I blogged about the automatic install of SOASuite and ServiceBus 12.2.1. It catered for the installation of the binaries and the creation of a repository.

What it does not handles is the creation of a domain. The last few weeks I worked on a script to do that. It's based on a wlst script for 12.1.3 by Edwin Biemond. This one was quite educational for me. But as denoted: it was made for 12.1.3. And I wanted more control on what and how certain artefacts are created. So gave it my own swing, also to adapt it for some wishes of my current customer.

Although there are certainly some improvements thinkable, and probably will be done in future uses, for now it is ready to share with the world.

One of the changes I did was to divide the main function in sections to make clear what the structure of the script is. I also moved some of the duplicate code or functional parts into separate functions.

Let me describe the sections first.

1. Create Base domain

The script starts with creating a base domain. It reads the default wls template 'wls.jar'. Sets the log properties of the domain. It then adapts the AdminServer to
  • change name as set in the property file: you can have your own naming convention.
  • change listen addres + port
  • Set default SSL settings
  • Set log file properties.
Then set the admin password on the domain and set the server start mode. Save the domain.
It then creates boot.properties files for nodemanager and AdminServer and set the password of the NodeManager. Finally setting the Applications home folder.

2. Extending domain with templates

The second section extends the domain with templates. Another improvement I did is that you can select which components you want to add by toggling the appropriate 'xxxEnabled' switches  in the property file, where xxx stands for the component (for instance 'soa', 'bpm', 'osb', 'bam', 'ess', etc.)

It supports the following components:
  • ServiceBus
  • SOA and BPM Suite and B2B
  • BAM
  • Enterprise Scheduler Service
Components such as  Managed File Transfer can be added quite easily.

3. DataSources

Section 3 takes care of setting the datasources to the created repository based on the repository user '{Prefix}_STB', via the 'LocalScvTblDataSource' datasource. In the property file you need to set
  • soaRepositoryDbUrl: jdbc connect string to the repository database
  • soaRepositoryDbUserPrefix=prefix used in the Repository creation 
  • soaRepositoryStbPwd=Password for the {Prefix}_STB user.
 It will adapt the LocalScvTblDataSource with these properties to load the information on the other repository users to create and adapt the datasources that are essential to the modules with which the domain is extended. These datasources are then made XA-Enabled.

4. Create UnixMachines, Clusters and Managed Servers

This section creates Machine definitions of type 'Unix', based on the properties:
  • server1Address=darlin-vce-db.darwin-it.local
  • server1Machine=darlin-vce-db
  • server2Enabled=true
  • server2Address=darlin-vce-db2.darwin-it.local
  • server2Machine=darlin-vce-db2
The machine denoted with 'server1Machine' is created always. The AdminServer is added to it, as well all the first, default, managed servers. The machine denoted with the property 'server2Machine' is only created when 'server2Enabled' is set to true.

I realize that 'server' in this context might be a little confusing. In serverYAddress and  serverYMachine, I actually mean a server-host, not a managed or admin server.

 For each component to configure (soa, osb, etc.) a cluster, denoted with for instance osbClr or soaClr, is created.

When you extend the domain with SOA Suite or OSB then automatically a managed server called 'soa_server1' or 'osb_server1' created with the appropriate deployments targeted to it. In the script of Edwin these are removed and new ones are created. I found problems with that and found that it's quite unnecessary, since we can rename the current ones with the given name in the property file, denoted with soaSvr1 or osbSvr1, etc., as is done with the AdminServer. So I leave the already created ones, but rename them to the desired value.

These first servers are added to the appropriate cluster, what causes to re-target the deployments to that cluster, magically.

Then if enabled, as with osbSvr2Enabled or soaSvr2Enabled, etc., the particular 'second' servers are created and added to the particular cluster.

5. Add Servers to ServerGroups

New in 12c is the concept of ServerGroups. In 11g you had only one definition of USER_MEM_ARGS in the setDomainEnv.sh/cmd. So these counted for each server (admin or managed) that are started using the start(Managed)Weblogic.sh/cmd scripts. But in 12c the determination of the USER_MEM_ARGS are done in a separate script: setStartupEnv.sh/cmd.
In this script the determination is done based on so-called ServerGroups. This provides a means to differentiate in memory settings for the particular servers, which was lacking in 11g.

So in this sections all the Managed and Admin Servers are added to a particular ServerGroup.

6. Create boot properties files

Lastly, for each created managed server a boot.properties file with the username password is created. Smart: I used to do this every single time by hand...

The example property file

Here's an example of the property file:
#############################################################################
# Properties voor Creeëren SOADomain
#
# @author Martien van den Akker, Darwin-IT Professionals
# @version 1.0, 2016-04-15
#
#############################################################################
#
fmwHome=/u01/app/oracle/FMW12210
#
soaDomainName=osb_domain
domainsHome=/u01/app/work/domains
applicationsHome=/u01/app/work/applications
productionMode=true
#
# Server Settings
server1Address=darlin-vce-db.darwin-it.local
server1Machine=darlin-vce-db
server2Enabled=true
server2Address=darlin-vce-db2.darwin-it.local
server2Machine=darlin-vce-db2
#
# Properties for AdminServer
adminServerName=AdminServer
adminListenAddress=darlin-vce-db
adminListenPort=7001
adminJavaArgsBase=-XX:PermSize=256m -XX:MaxPermSize=512m -Xms1024m -Xmx1532m
# Properties for OSB
osbEnabled=true
osbJavaArgsBase=-XX:PermSize=256m -XX:MaxPermSize=512m -Xms1024m -Xmx1024m
osbClr=OsbCluster
osbSvr1=OsbServer1
osbSvr1Port=8011
osbSvr2Enabled=true
osbSvr2=OsbServer2
osbSvr2Port=8012
# Properties for SOA
soaEnabled=true
bpmEnabled=true
b2bEnabled=true
soaJavaArgsBase=-XX:PermSize=256m -XX:MaxPermSize=752m -Xms1024m -Xmx1532m
soaClr=SoaCluster
soaSvr1=SoaServer1
soaSvr1Port=8001
soaSvr2Enabled=true
soaSvr2=SoaServer2
soaSvr2Port=8002
# Properties for ESS
essEnabled=true
essJavaArgsBase=-XX:PermSize=256m -XX:MaxPermSize=512m -Xms1024m -Xmx1024m
essClr=essCluster
essSvr1=EssServer1
essSvr1Port=8021
essSvr2Enabled=true
essSvr2=EssServer2
essSvr2Port=8022
# Properties for BAM
bamEnabled=true
bamJavaArgsBase=-XX:PermSize=256m -XX:MaxPermSize=512m -Xms1024m -Xmx1532m
bamClr=BamCluster
bamSvr1=BamServer1
bamSvr1Port=9001
bamSvr2Enabled=true
bamSvr2=BamServer2
bamSvr2Port=9002
# AdminUser
adminUser=weblogic
adminPwd=welcome1
# SoaRepository Settings
soaRepositoryDbUrl=jdbc:oracle:thin:@darlin-vce-db.darwin-it.local:1521/pdborcl
soaRepositoryDbUserPrefix=DEV
soaRepositoryStbPwd=DEV_STB
# Logs
logsHome=/u01/app/work/logs
fileCount=10
fileMinSize=5000
fileTimeSpan=24
rotationType=byTime
#
# Settings
webtierEnabled=false
jsseEnabled=false

Save it with a name like darlin-vce-db.properties, but adapted for each particular environment.

The script

(And of course I don't mean the band that my daughter likes...)
#############################################################################
# Create a SOA/BPM/OSB domain
#
# @author Martien van den Akker, Darwin-IT Professionals
# @version 1.0, 2016-04-09
#
#############################################################################
# Modify these values as necessary
import sys, traceback
scriptName = 'createSoaBpmDomain.py'
#
#Home Folders
wlsHome    = fmwHome+'/wlserver'
soaDomainHome       = domainsHome+'/'+soaDomainName
soaApplicationsHome = applicationsHome+'/'+soaDomainName
#
# Templates for 12.1.3
#wlsjar =fmwHome+'/wlserver/common/templates/wls/wls.jar'
#oracleCommonTplHome=fmwHome+'/oracle_common/common/templates'
#wlservicetpl=oracleCommonTplHome+'/oracle.wls-webservice-template_12.1.3.jar'
#osbtpl=fmwHome+'/osb/common/templates/wls/oracle.osb_template_12.1.3.jar'
#applCoreTpl=oracleCommonTplHome+'/wls/oracle.applcore.model.stub.1.0.0_template.jar'
#soatpl=fmwHome+'/soa/common/templates/wls/oracle.soa_template_12.1.3.jar'
#bamtpl=fmwHome+'/soa/common/templates/wls/oracle.bam.server_template_12.1.3.jar'
#bpmtpl=fmwHome+'/soa/common/templates/wls/oracle.bpm_template_12.1.3.jar'
#essBasicTpl=oracleCommonTplHome+'/wls/oracle.ess.basic_template_12.1.3.jar'
#essEmTpl=fmwHome+'/em/common/templates/wls/oracle.em_ess_template_12.1.3.jar'
#ohsTpl=fmwHome+'/ohs/common/templates/wls/ohs_managed_template_12.1.3.jar'
#b2bTpl=fmwHome+'/soa/common/templates/wls/oracle.soa.b2b_template_12.1.3.jar'
#
# Templates for 12.2.1
wlsjar =fmwHome+'/wlserver/common/templates/wls/wls.jar'
oracleCommonTplHome=fmwHome+'/oracle_common/common/templates'
wlservicetpl=oracleCommonTplHome+'/wls/oracle.wls-webservice-template.jar'
osbtpl=fmwHome+'/osb/common/templates/wls/oracle.osb_template.jar'
applCoreTpl=oracleCommonTplHome+'/wls/oracle.applcore.model.stub_template.jar'
soatpl=fmwHome+'/soa/common/templates/wls/oracle.soa_template.jar'
bamtpl=fmwHome+'/soa/common/templates/wls/oracle.bam.server_template.jar'
bpmtpl=fmwHome+'/soa/common/templates/wls/oracle.bpm_template.jar'
essBasicTpl=oracleCommonTplHome+'/wls/oracle.ess.basic_template.jar'
essEmTpl=fmwHome+'/em/common/templates/wls/oracle.em_ess_template.jar'
ohsTpl=fmwHome+'/ohs/common/templates/wls/ohs_managed_template.jar' # need to be validated!
b2bTpl=fmwHome+'/soa/common/templates/wls/oracle.soa.b2b_template.jar' # need to be validated!
#
# ServerGroup definitions
adminSvrGrpDesc='WSM-CACHE-SVR WSMPM-MAN-SVR JRF-MAN-SVR'
adminSvrGrp=["WSM-CACHE-SVR" , "WSMPM-MAN-SVR" , "JRF-MAN-SVR"]
essSvrGrpDesc="ESS-MGD-SVRS"
essSvrGrp=["ESS-MGD-SVRS"]
soaSvrGrpDesc="SOA-MGD-SVRS"
soaSvrGrp=["SOA-MGD-SVRS"]
bamSvrGrpDesc="BAM12-MGD-SVRS"
bamSvrGrp=["BAM12-MGD-SVRS"]
osbSvrGrpDesc="OSB-MGD-SVRS-COMBINED"
osbSvrGrp=["OSB-MGD-SVRS-COMBINED"]
#
#
lineSeperator='__________________________________________________________________________________'
#
#
def usage():
  print 'Call script as: '
  print 'Windows: wlst.cmd '+scriptName+' -loadProperties localhost.properties'
  print 'Linux: wlst.sh '+scriptName+' -loadProperties environment.properties'
  print 'Property file should contain the following properties: '
  print "adminUrl='localhost:7101'"
  print "adminUser='weblogic'"
  print "adminPwd='welcome1'"
#
# Create a boot properties file.
def createBootPropertiesFile(directoryPath,fileName, username, password):
  print ('Create Boot Properties File for folder: '+directoryPath)
  print (lineSeperator)
  serverDir = File(directoryPath)
  bool = serverDir.mkdirs()
  fileNew=open(directoryPath + '/'+fileName, 'w')
  fileNew.write('username=%s\n' % username)
  fileNew.write('password=%s\n' % password)
  fileNew.flush()
  fileNew.close()
#
# Create Startup Properties File
def createAdminStartupPropertiesFile(directoryPath, args):
  print 'Create AdminServer Boot Properties File for folder: '+directoryPath
  print (lineSeperator)
  adminserverDir = File(directoryPath)
  bool = adminserverDir.mkdirs()
  fileNew=open(directoryPath + '/startup.properties', 'w')
  args=args.replace(':','\\:')
  args=args.replace('=','\\=')
  fileNew.write('Arguments=%s\n' % args)
  fileNew.flush()
  fileNew.close()
 #
# Set Log properties
def setLogProperties(logMBeanPath, logFile, fileCount, fileMinSize, rotationType, fileTimeSpan):
  print '\nSet Log Properties for: '+logMBeanPath
  print (lineSeperator)
  cd(logMBeanPath)
  print ('Server log path: '+pwd())
  print '. set FileName to '+logFile
  set('FileName'    ,logFile)
  print '. set FileCount to '+str(fileCount)
  set('FileCount'   ,int(fileCount))
  print '. set FileMinSize to '+str(fileMinSize)
  set('FileMinSize' ,int(fileMinSize))
  print '. set RotationType to '+rotationType
  set('RotationType',rotationType)
  print '. set FileTimeSpan to '+str(fileTimeSpan)
  set('FileTimeSpan',int(fileTimeSpan))
#
#
def createServerLog(serverName, logFile, fileCount, fileMinSize, rotationType, fileTimeSpan):
  print ('\nCreate Log for '+serverName)
  print (lineSeperator)
  cd('/Server/'+serverName)
  create(serverName,'Log')
  setLogProperties('/Server/'+serverName+'/Log/'+serverName, logFile, fileCount, fileMinSize, rotationType, fileTimeSpan)
#
# Change DataSource to XA
def changeDatasourceToXA(datasource):
  print 'Change datasource '+datasource
  print (lineSeperator)
  cd('/')
  cd('/JDBCSystemResource/'+datasource+'/JdbcResource/'+datasource+'/JDBCDriverParams/NO_NAME_0')
  set('DriverName','oracle.jdbc.xa.client.OracleXADataSource')
  print '. Set UseXADataSourceInterface='+'True'
  set('UseXADataSourceInterface','True') 
  cd('/JDBCSystemResource/'+datasource+'/JdbcResource/'+datasource+'/JDBCDataSourceParams/NO_NAME_0')
  print '. Set GlobalTransactionsProtocol='+'TwoPhaseCommit'
  set('GlobalTransactionsProtocol','TwoPhaseCommit')
  cd('/')
#
#
def createCluster(cluster):
  print ('\nCreate '+cluster)
  print (lineSeperator)
  cd('/')
  create(cluster, 'Cluster')
#
# Create a Unix Machine
def createUnixMachine(serverMachine,serverAddress):
  print('\nCreate machine '+serverMachine+' with type UnixMachine')
  print (lineSeperator)
  cd('/')
  create(serverMachine,'UnixMachine')
  cd('UnixMachine/'+serverMachine)
  create(serverMachine,'NodeManager')
  cd('NodeManager/'+serverMachine)
  set('ListenAddress',serverAddress)
#
# Add server to  Unix Machine
def addServerToMachine(serverName, serverMachine):
  print('\nAdd server '+serverName+' to '+serverMachine)
  print (lineSeperator)
  cd('/Servers/'+serverName)
  set('Machine',serverMachine)
#
# Determine the Server Java Args
def getServerJavaArgs(serverName,javaArgsBase,logsHome):
  javaArgs = javaArgsBase+' -Dweblogic.Stdout='+logsHome+'/'+serverName+'.out -Dweblogic.Stderr='+logsHome+'/'+serverName+'_err.out'
  return javaArgs
#
# Change Managed Server
def changeManagedServer(server,listenAddress,listenPort,javaArgs):
  print '\nChange ManagedServer '+server
  print (lineSeperator)
  cd('/Servers/'+server)
  print '. Set listen address and port to: '+listenAddress+':'+str(listenPort)
  set('ListenAddress',listenAddress)
  set('ListenPort'   ,int(listenPort))
  # ServerStart
  print ('. Create ServerStart')
  create(server,'ServerStart')
  #cd('ServerStart/'+server)
  #print ('. Set Arguments to: '+javaArgs)
  #set('Arguments' , javaArgs)
  # SSL
  cd('/Server/'+server)
  print ('. Create server SSL')
  create(server,'SSL')
  cd('SSL/'+server)
  print ('. Set SSL Enabled to: '+'False')
  set('Enabled'                    , 'False')
  print ('. Set SSL HostNameVerificationIgnored to: '+'True')
  set('HostNameVerificationIgnored', 'True')
  #
  if jsseEnabled == 'true':
    print ('. Set JSSEEnabled to: '+ 'True')
    set('JSSEEnabled','True')
  else:
    print ('. Set JSSEEnabled to: '+ 'False')
    set('JSSEEnabled','False')
#
# Create a Managed Server
def createManagedServer(server,listenAddress,listenPort,cluster,machine,
                        javaArgsBase,fileCount,fileMinSize,rotationType,fileTimeSpan):
  print('\nCreate '+server)
  print (lineSeperator)
  cd('/')
  create(server, 'Server')
  cd('/Servers/'+server)
  javaArgs=getServerJavaArgs(server,javaArgsBase,logsHome)
  changeManagedServer(server,listenAddress,listenPort,javaArgs)
  createServerLog(server, logsHome+'/'+server+'.log', fileCount, fileMinSize, rotationType, fileTimeSpan)
  print('Add '+server+' to cluster '+cluster)
  cd('/')
  assign('Server',server,'Cluster',cluster)
  addServerToMachine(server, machine)
#
# Adapt a Managed Server
def adaptManagedServer(server,newSrvName,listenAddress,listenPort,cluster,machine,
                       javaArgsBase,fileCount,fileMinSize,rotationType,fileTimeSpan):
  print('\nAdapt '+server)
  print (lineSeperator)
  cd('/')
  cd('/Servers/'+server)
  # name of adminserver
  print '. Rename '+server+' to '+ newSrvName
  set('Name',newSrvName )
  cd('/Servers/'+newSrvName)
  javaArgs=getServerJavaArgs(newSrvName,javaArgsBase,logsHome)
  changeManagedServer(newSrvName,listenAddress,listenPort,javaArgs)
  createServerLog(newSrvName, logsHome+'/'+newSrvName+'.log', fileCount, fileMinSize, rotationType, fileTimeSpan)
  print('Add '+newSrvName+' to cluster '+cluster)
  cd('/')
  assign('Server',newSrvName,'Cluster',cluster)
  addServerToMachine(newSrvName, machine)
#
# Change Admin Server
def changeAdminServer(adminServerName,listenAddress,listenPort,javaArguments):
  print '\nChange AdminServer'
  print (lineSeperator)
  cd('/Servers/AdminServer')
  # name of adminserver
  print '. Set Name to '+ adminServerName
  set('Name',adminServerName )
  cd('/Servers/'+adminServerName)
  # address and port
  print '. Set ListenAddress to '+ server1Address
  set('ListenAddress',server1Address)
  print '. Set ListenPort to '+ str(listenPort)
  set('ListenPort'   ,int(listenPort))
  #
  # ServerStart
  print 'Create ServerStart'
  create(adminServerName,'ServerStart')
  #cd('ServerStart/'+adminServerName)
  #print '. Set Arguments to: '+javaArguments
  #set('Arguments' , javaArguments)
  # SSL
  cd('/Server/'+adminServerName)
  print 'Create SSL'
  create(adminServerName,'SSL')
  cd('SSL/'+adminServerName)
  set('Enabled'                    , 'False')
  set('HostNameVerificationIgnored', 'True')
  #
  if jsseEnabled == 'true':
    print ('. Set JSSEEnabled to: '+ 'True')
    set('JSSEEnabled','True')
  else:
    print ('. Set JSSEEnabled to: '+ 'False')
    set('JSSEEnabled','False')
#
#
def main():
  try:
    #
    # Section 1: Base Domain + Admin Server
    print (lineSeperator)
    print ('1. Create Base domain '+soaDomainName)
    print('\nCreate base wls domain with template '+wlsjar)
    print (lineSeperator)
    readTemplate(wlsjar)
    #
    cd('/')
    # Domain Log
    print('Set base_domain log')
    create('base_domain','Log')
    setLogProperties('/Log/base_domain', logsHome+soaDomainName+'.log', fileCount, fileMinSize, rotationType, fileTimeSpan)    
    #
    # Admin Server
    adminJavaArgs = getServerJavaArgs(adminServerName,adminJavaArgsBase,logsHome)
    changeAdminServer(adminServerName,adminListenAddress,adminListenPort,adminJavaArgs)
    createServerLog(adminServerName, logsHome+adminServerName+'.log', fileCount, fileMinSize, rotationType, fileTimeSpan)   
    #
    print('\nSet password in '+'/Security/base_domain/User/weblogic')
    cd('/')
    cd('Security/base_domain/User/weblogic')
    # weblogic user name + password
    print('. Set Name to: ' +adminUser)
    set('Name',adminUser)
    cmo.setPassword(adminPwd)
    #
    if productionMode == 'true':
      print('. Set ServerStartMode to: ' +'prod')
      setOption('ServerStartMode', 'prod')
    else:
      print('. Set ServerStartMode to: ' +'dev')
      setOption('ServerStartMode', 'dev')
    #
    print('write Domain...')
    # write path + domain name
    writeDomain(soaDomainHome)
    closeTemplate()
    #
    createAdminStartupPropertiesFile(soaDomainHome+'/servers/'+adminServerName+'/data/nodemanager',adminJavaArgs)
    createBootPropertiesFile(soaDomainHome+'/servers/'+adminServerName+'/security','boot.properties',adminUser,adminPwd)
    createBootPropertiesFile(soaDomainHome+'/config/nodemanager','nm_password.properties',adminUser,adminPwd)
    #
    es = encrypt(adminPwd,soaDomainHome)
    #
    readDomain(soaDomainHome)
    #
    print('set Domain password for '+soaDomainName) 
    cd('/SecurityConfiguration/'+soaDomainName)
    set('CredentialEncrypted',es)
    #
    print('Set nodemanager password')
    set('NodeManagerUsername'         ,adminUser )
    set('NodeManagerPasswordEncrypted',es )
    #
    cd('/')
    setOption( "AppDir", soaApplicationsHome )
    #
    print('Finished base domain.')
    #
    # Section 2: Templates
    print('\n2. Extend Base domain with templates.')
    print (lineSeperator)
    print ('Adding Webservice template '+wlservicetpl)
    addTemplate(wlservicetpl)
    # SOA Suite
    if soaEnabled == 'true':
      print ('Adding SOA Template '+soatpl)    
      addTemplate(soatpl)
    else:
      print('SOA is disabled')
    # BPM
    if bpmEnabled == 'true':
      print ('Adding BPM Template '+bpmtpl)
      addTemplate(bpmtpl)
    else:
      print('BPM is disabled')
    # OSB
    if osbEnabled == 'true':
      print ('Adding OSB template '+osbtpl)
      addTemplate(osbtpl)
    else:
      print('OSB is disabled')
    #
    print ('Adding ApplCore Template '+applCoreTpl)
    addTemplate(applCoreTpl)
    #
    if bamEnabled == 'true':
      print ('Adding BAM Template '+bamtpl)
      addTemplate(bamtpl)
    else:
      print ('BAM is disabled')
    #
    if webtierEnabled == 'true' == true:
      print ('Adding OHS Template '+ohsTpl)
      addTemplate(ohsTpl)
    else:
      print('OHS is disabled') 
    #      
    if b2bEnabled == 'true':
      print 'Adding B2B Template '+b2bTpl
      addTemplate(b2bTpl)
    else:
      print('B2B is disabled')
    #
    if essEnabled == 'true':
      print ('Adding ESS Template'+essBasicTpl)
      addTemplate(essBasicTpl)
      print ('Adding ESS Em Template'+essEmTpl)
      addTemplate(essEmTpl)
    else:
      print('ESS is disabled')
    # 
    dumpStack()
    print ('Finished templates')
    #
    # Section 3: Change Datasources
    print ('\n3. Change datasources')
    print 'Change datasource LocalScvTblDataSource'
    cd('/JDBCSystemResource/LocalSvcTblDataSource/JdbcResource/LocalSvcTblDataSource/JDBCDriverParams/NO_NAME_0')
    set('URL',soaRepositoryDbUrl)
    set('PasswordEncrypted',soaRepositoryStbPwd)
    cd('Properties/NO_NAME_0/Property/user')
    set('Value',soaRepositoryDbUserPrefix+'_STB')
    #
    print ('Call getDatabaseDefaults which reads the service table')
    getDatabaseDefaults()    
    #
    if soaEnabled == 'true':
      changeDatasourceToXA('EDNDataSource')
    if osbEnabled == 'true':
      changeDatasourceToXA('wlsbjmsrpDataSource')
    changeDatasourceToXA('OraSDPMDataSource')
    changeDatasourceToXA('SOADataSource')
    #
    if bamEnabled == 'true':
      changeDatasourceToXA('BamDataSource')
    #
    print 'Finshed DataSources'
    #
    # Section 4: Create UnixMachines, Clusters and Managed Servers
    print ('\n4. Create UnixMachines, Clusters and Managed Servers')
    print (lineSeperator)
    cd('/')
    #
    createUnixMachine(server1Machine,server1Address)
    if server2Enabled == 'true':
      createUnixMachine(server2Machine,server2Address)
    #
    addServerToMachine(adminServerName,server1Machine)
    #
    cd('/')
   # SOA Suite
    if soaEnabled == 'true':
      createCluster(soaClr)
      adaptManagedServer('soa_server1',soaSvr1,server1Address, soaSvr1Port,soaClr,server1Machine,
                         soaJavaArgsBase,fileCount,fileMinSize,rotationType,fileTimeSpan)
      if soaSvr2Enabled == 'true':                   
        createManagedServer(soaSvr2,server2Address,soaSvr2Port,soaClr,server2Machine,
                            soaJavaArgsBase,fileCount,fileMinSize,rotationType,fileTimeSpan)
      else:
        print('Do not create SOA Server2')
    #
   # OSB
    if osbEnabled == 'true':
      createCluster(osbClr)
      adaptManagedServer('osb_server1',osbSvr1,server1Address,osbSvr1Port,osbClr,server1Machine,
                         osbJavaArgsBase,fileCount,fileMinSize,rotationType,fileTimeSpan)
      if osbSvr2Enabled == 'true':                   
        createManagedServer(osbSvr2,server2Address,osbSvr2Port,osbClr,server2Machine,
                            osbJavaArgsBase,fileCount,fileMinSize,rotationType,fileTimeSpan)
      else:
        print('Do not create OSB Server2')      
    #
   # BAM
    if bamEnabled == 'true':
      createCluster(bamClr)
      adaptManagedServer('bam_server1',bamSvr1,server1Address,bamSvr1Port,bamClr,server1Machine,
                         bamJavaArgsBase,fileCount,fileMinSize,rotationType,fileTimeSpan)
      if bamSvr2Enabled == 'true':                   
        createManagedServer(bamSvr2,server2Address,bamSvr2Port,bamClr,server2Machine,
                            bamJavaArgsBase,fileCount,fileMinSize,rotationType,fileTimeSpan)
      else:
        print('Do not create BAM Server2')
   #
    # ESS
    if essEnabled == 'true':
      createCluster(essClr)
      adaptManagedServer('ess_server1',essSvr1,server1Address,essSvr1Port,essClr,server1Machine,
                         essJavaArgsBase,fileCount,fileMinSize,rotationType,fileTimeSpan)
      if essSvr2Enabled == 'true':                   
        createManagedServer(essSvr2,server2Address,essSvr2Port,essClr,server2Machine,
                            essJavaArgsBase,fileCount,fileMinSize,rotationType,fileTimeSpan)
      else:
        print('Do not create ESS Server2')

    #
    print ('Finshed creating Machines, Clusters and ManagedServers')
    #
    # Section 5: Add Servers to ServerGroups.
    print ('\n5. Add Servers to ServerGroups')
    print (lineSeperator)
    cd('/')
    print 'Add server groups '+adminSvrGrpDesc+ ' to '+adminServerName
    setServerGroups(adminServerName, adminSvrGrp)                      
    # SOA
    if soaEnabled == 'true':
      print 'Add server group '+soaSvrGrpDesc+' to '+soaSvr1+' and possibly '+soaSvr2
      setServerGroups(soaSvr1, soaSvrGrp)
      if soaSvr2Enabled == 'true': 
        setServerGroups(soaSvr2, soaSvrGrp)
    #      
    # OSB
    if osbEnabled == 'true':
      print 'Add server group '+osbSvrGrpDesc+' to '+osbSvr1+' and possibly '+osbSvr2
      setServerGroups(osbSvr1, osbSvrGrp)
      if osbSvr2Enabled == 'true': 
        setServerGroups(osbSvr2, osbSvrGrp)
    #
    if bamEnabled == 'true':
      print 'Add server group '+bamSvrGrpDesc+' to '+bamSvr1+' and possibly '+bamSvr2
      setServerGroups(bamSvr1, bamSvrGrp)
      if bamSvr2Enabled == 'true': 
        setServerGroups(bamSvr2, bamSvrGrp)    
    #
    if essEnabled == 'true':
      print 'Add server group '+essSvrGrpDesc+' to '+essSvr1+' and possibly '+essSvr2
      setServerGroups(essSvr1, essSvrGrp)
      if essSvr2Enabled == 'true': 
        setServerGroups(essSvr2, essSvrGrp)
    #
    print ('Finshed ServerGroups.')
    #
    updateDomain()
    closeDomain();
    #
    # Section 6: Create boot properties files.
    print ('\n6. Create boot properties files')
    print (lineSeperator)
    # SOA
    if soaEnabled == 'true':
      createBootPropertiesFile(soaDomainHome+'/servers/'+soaSvr1+'/security','boot.properties',adminUser,adminPwd)
      if soaSvr2Enabled == 'true': 
        createBootPropertiesFile(soaDomainHome+'/servers/'+soaSvr2+'/security','boot.properties',adminUser,adminPwd)
    #
    # OSB
    if osbEnabled == 'true':
      createBootPropertiesFile(soaDomainHome+'/servers/'+osbSvr1+'/security','boot.properties',adminUser,adminPwd)
      if osbSvr2Enabled == 'true': 
        createBootPropertiesFile(soaDomainHome+'/servers/'+osbSvr2+'/security','boot.properties',adminUser,adminPwd)
    #
    if bamEnabled == 'true':
      createBootPropertiesFile(soaDomainHome+'/servers/'+bamSvr1+'/security','boot.properties',adminUser,adminPwd)
      if bamSvr2Enabled == 'true': 
        createBootPropertiesFile(soaDomainHome+'/servers/'+bamSvr1+'/security','boot.properties',adminUser,adminPwd)
    #
    if essEnabled == 'true':
      createBootPropertiesFile(soaDomainHome+'/servers/'+essSvr1+'/security','boot.properties',adminUser,adminPwd)
      if essSvr2Enabled == 'true': 
        createBootPropertiesFile(soaDomainHome+'/servers/'+essSvr2+'/security','boot.properties',adminUser,adminPwd)
    #
    print ('\nFinished')
    #
    print('\nExiting...')
    exit()
  except NameError, e:
    print 'Apparently properties not set.'
    print "Please check the property: ", sys.exc_info()[0], sys.exc_info()[1]
    usage()
  except:
    apply(traceback.print_exception, sys.exc_info())
    stopEdit('y')
    exit(exitcode=1)
#call main()
main()
exit()

Conclusion

As said, although I think this script is already quite adaptable using the property file, of course there are many improvements thinkable for your particular situation. It creates a 'skeleton' SOA or Service Bus domain, but you might need to adapt for network topologies, security settings.
And although it creates a 'per domain' nodemanager configuration, you would need to adapt it for your particular needs to get the domain started. I only tested this by starting the Admin server using the startWeblogic.sh script.

Having such a script is such  a valuable asset: it allows you to (re-)create your domains repeatably in a standard way, ensuring that different environments (dev, test, acc, prod) are created similarly.

One, last thing though, the script somehow registers the creation of the domain and thus the use of the datasources in the repository. So you can't just throw away the domain and recreate it to the current Repository. You'll need to rereate the Repository as well.