Go to the below link to know more about it.
Thursday, December 16, 2010
Findbug - A useful tool for Java developer
Go to the below link to know more about it.
Thursday, December 2, 2010
Steps to disable OOM Killer on Linux
To disable OOM, follow the below steps.
Check status of oom-killer:
# cat /proc/sys/vm/oom-kill
Turn oom-killer off/on:
# echo "0" > /proc/sys/vm/oom-kill
# echo "1" > /proc/sys/vm/oom-kill
To make this change take effect at boot time, add the following
to /etc/sysctl.conf:
vm.oom-kill = 0
For processes that would have been killed, but weren't because the oom-
killer is disabled, you'll see the following message
in /var/log/messages:
"Would have oom-killed but /proc/sys/vm/oom-kill is disabled"
Monday, November 22, 2010
OID LDAP commands
./ldapadd -h hostname.company.com -p 389 -D cn=orcladmin -w passwd -f /home/oracle/software/New_Oid_Users.txt
Delete Users
./ldapdelete -h hostname.company.com -p 389 -D cn=orcladmin -w passwd -c -v -f /home/oracle/software/delete_users.txt
OIM: Installing the Design Console under Linux
http://www.idmworks.com/blog/oim-installing-the-design-console-under-linux
It does work!!
Thursday, November 18, 2010
Make OIM portal load faster
This can cause the home page to load slowly.
Since this information is not of much use in many organization on the home page, we can disable this additional SQL search and make sure only a link is placed on the home page to get to that same data.
To disable these counters, please execute below steps:
1) Login to Design Console with 'xelsysadm'
2) Go to Administration->> System Configuration
3) Search for the keyword "XL.WebAdminHome.CounterDisplay.Type"
4) Change the values from 'DayLimit' to 'CheckLink'
Now when a user logs in OIM he will see a link instead of counters.
For many users, the home page will be much faster from now on.
Special characters not allowed in name fields - OIM
- User Login
- Group Name
- Organization Name
- Resource Name
- Process Name
- Request Number
- Column Label
- Task Name
- Semicolon ( ; )
- Pound ( # )
- Forward slash ( / )
- Percent ( % )
- Equals sign ( = )
- Bar ( | )
- Plus sign ( + )
- Comma ( , )
- Back slash ( \ )
- Double quotes ( " )
- Less than ( < )
- Greater than ( > )
If you are using your own form to create a user in OIM, make sure to restrict the above characters in the preceding fields.
The allowed special characters are as below :
! * @ $ ( ) ^ ? { } [ ] : ' ~ ` - _
Put these in a common regular expression pattern.
OIM user email field character restriction
"The local-part and domain name portions of an e-mail address are restricted to ASCII letters, numbers, underscores, hyphens, and periods. The domain identifier portion of an e-mail address is restricted to ASCII letters and numbers"
(http://download.oracle.com/docs/cd/B32479_01/doc.903/b32455/componts.htm#CIHCCEAF)
But this restriction is removed in OIM 9.0.2 version. Below regular expression defined the characters allowed in the email address field in version 9.0.2
([\\w!#$%&'*+-/=?^_`{|}~])+[@](\\w|[-]|[.])+[.]([a-zA-Z0-9])+
So if you need all those special characters, you will have to make sure you are using 9.0.2 and not 9.0.1.
It is a patch update to upgrade from 9.0.1 to 9.0.2. So it should not be difficult.
Auto Approve Self Registered Users in OIM
By default, a self registered user will have to be approved by a administrator. To automatically approve Self Registered Users in OIM
1. Open the OIM_HOME/xellerate/config/FormMetadata.xml file.
2. Copy the following lines from <form name="SelfRegistrationApprovalForm"> to <form name="SelfRegistrationUserForm"> section:
<attributereference editable="true" optional="true">Organizations.Organization Name</attributereference>
<attributereference editable="true" optional="false">Users.Xellerate Type</attributereference>
<attributereference editable="true" optional="true">Users.Role</attributereference>
3. Save changes and restart the OIM Server.
4. Now when you try to do a Self Register, you need to enter values for Organization name, User Type and Employee Role.
5. Enter value for User Type as End-User.
6. Enter value for Employee Role as Full-Time (Note: The value should be Full-Time and not Full-Time Employee).
7. Submit request, and the request would get automatically approved.
New Blog for Identity Management
So if you are looking for any help of identity management, please check the below link.
http://identity-corp.blogspot.com/
Delete OID users and Groups
To delete all OID users, please follow the below steps.
Run ldapsearch to extract the names of all users.
ldapsearch -x -h oidserver.corp.company.com -p 389 -D cn=orcladmin -w passwd -L -b "cn=users,dc=corp,dc=company,dc=com" -s one "objectclass=*" dn > current_users.txt
From the file that is generated, Delete the text dn: (Use find - replace to do this). Also make sure that every entry is in one single line.
Run the ldapdelete to delete all the users from the file.
ldapdelete -x -h oidserver.corp.company.com -p 389 -D cn=orcladmin -w passwd -c -v -f current_users.txt
Login to oidadmin to confirm the delete.
Run the same for cn=Groups to delete all groups.
Migrate or copy users from one OID to another
Below steps will guide you on how to do it. (These commands are for Linux)
On the source OID
Set the $oracle_home appropriately. Example is below
$export ORACLE_HOME=/oracle/db/product/oid/
Go to $ORACLE_HOME/ldap/bin FOLDER
$cd $ORACLE_HOME/ldap/bin
Run the following command to export the users
$ldapsearch -x -h oidserver.corp.company.com -p 389 -D cn=orcladmin -w password -L -b "cn=users,dc=oidserver,dc=corp,dc=company,dc=com" -s one "objectclass=*" dn o cn gcpcompanycode givenname mail objectclass oimuserkey preferredlanguage sn telephonenumber userpassword > oid_filteruser.txt
Run the following command to export the groups
$ldapsearch -x -h oidserver.corp.company.com -p 389 -D cn=orcladmin -w password -L -b "cn=Groups,dc=oidserver,dc=corp,dc=company,dc=com" -s one "objectclass=*" > oid_filtergroup.txt
Copy the files generated (in this case oid_filteruser.txt and oid_filtergroup.txt) to the destination OID
$scp oid_filteruser.txt oid_filtergroup.txt otherOidServer.corp.company.com:/oracle/db/oid_files/.
On the destination OID
If this is a existing environment, then delete the existing users and groups before you import new users. To know how to delete the users, follow this link Delete Users in OID
Stop OID
Set the $oracle_home as follows
$export ORACLE_HOME=/oracle/db/product/oid/
Go to $ORACLE_HOME/ldap/bin FOLDER
$cd $ORACLE_HOME/ldap/bin
Then run the following commands one after the other. (DEVOID is the schema name of OID)
./bulkload connect="DEVOID" generate=true load=true file="/oracle/db/oid_files/oid_filteruser.txt"
Enter the password when prompted. Import will them complete in few seconds.
./bulkload connect="DEVOID" generate=true load=true file="/oracle/db/oid_files/oid_filtergroup.txt"
Enter the password when prompted. Import will them complete in few seconds.
Start OID
Go to oidadmin console and verify.
Unlock users in OID
We must ideally create a file to do this so that we can use this multiple times.
Below are the steps.
- Create a file, say name it as unlockusers.sh.
- Paste the below content into the file.
ldapmodify -p 389 -h servername.corp.company.com -D cn=orcladmin -w password -x -v <<EOF
dn: cn=username, cn=Users,dc=corp,dc=company,dc=com
changetype: modify
add: orclpwdaccountunlock
orclpwdaccountunlock: 1
EOF Save and close the file. Give execute permission on that new file
chmod +x unlockuser.shExecute the file like
./unlockusers.sh
Next time on, all you need to do is change the username and execute the file.
Wednesday, June 9, 2010
WSIMPORT configuration in Maven
Below is the way how we do it.
Use "mvn -P wsimport" generate a fresh JAX-WS client, then check that client
into subversion as source. Whenever the service contract changes, run mvn wsimport again. (Each time you
run it with the wsimport profile, maven will clean the source folder and regenerate all the JAX-WS client Javas)
Use the following link to learn how to make a Spring config for Spring-loading the JAX-WS client:
http://static.springsource.org/spring/docs/2.5.x/reference/remoting.html#remoting-web-services-jaxws-access
Note: if you don't specify the wsimport profile with the "mvn -P wsimport" command, then this module simply builds
a straight java jar. This nice selective build feature of maven lets us checkin the jaxws java artifacts to
subversion, and lets us control when we do new java generation only when the service interface changes.
<profiles>
<profile>
<id>wsimport</id>
<build>
<defaultGoal>install</defaultGoal>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.2-beta-3</version>
<configuration>
<descriptorRefs>
<descriptorRef>
jar-with-dependencies
</descriptorRef>
</descriptorRefs>
<archiverConfig>
<duplicateBehavior>
skip
</duplicateBehavior>
</archiverConfig>
</configuration>
</plugin>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>2.2</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>clean</goal>
</goals>
<configuration>
<filesets>
<fileset>
<directory>
src/main/java/com/company/proj/serviceclient/module
</directory>
<includes>
<include>
**/*.java
</include>
<include>
**/*.properties
</include>
</includes>
<followSymlinks>
false
</followSymlinks>
</fileset>
</filesets>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxws-maven-plugin</artifactId>
<version>1.12</version>
<executions>
<execution>
<id>UserManagementClient</id>
<goals>
<goal>wsimport</goal>
</goals>
<configuration>
<keep>true</keep>
<sourceDestDir>src/main/java</sourceDestDir>
<xdebug>true</xdebug>
<verbose>true</verbose>
<target>2.1</target>
<extension>true</extension>
<bindingFiles><bindingFile>${basedir}/src/main/resources/generateElementProperty.binding</bindingFile></bindingFiles>
<!-- <xauthFile>${basedir}/src/main/resources/authFile</xauthFile> -->
<wsdlUrls>
<wsdlUrl>
http://server.companyname.com:8001/projectcontext/1.0.0?wsdl
</wsdlUrl>
</wsdlUrls>
<packageName>
com.equinix.gse.serviceclient.usermanagement
</packageName>
</configuration>
</execution>
</executions>
<!-- if you want to use a specific version of JAX-WS, you can do so like this -->
<!--dependencies>
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-tools</artifactId>
<version>2.1.7</version>
</dependency>
</dependencies-->
</plugin>
</plugins>
</build>
</profile>
</profiles>
Tuesday, June 8, 2010
Transaction Management using Spring
You can implement transaction management with a few configurations in the application context file of spring and no additions or alterations to the java code.
Below is the configuration which is required for single Database transaction management.
<!-- ***** Transaction Manager ****** -->
<bean id="txManager1"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="projectDataSource" />
</bean>
<!-- ****** Transaction Definitions ***** -->
<!-- Default Isolation level is used - READ COMMITTED -->
<tx:advice id="txAdvice1" transaction-manager="txManager1">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="allProcessOperations"
expression="execution(* com.company.proj.bo.process.*Process.*(..))" />
<aop:advisor advice-ref="txAdvice1"
pointcut-ref="allProcessOperations" />
</aop:config>
The projectDataSource is ofcource the datasource bean which you must define for your datasource.
The above configuration binds all the operations on the projectDataSource into one transaction.
The AOP pointcut "execution(* com.company.proj.bo.process.*Process.*(..))" indicates that all the methods under all the classes ending with "Process" under the package com.company.proj.bo.process will be considered in transaction management.
The TX and AOP schema locations are
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
Any exception during the execution of a method which is bound under the above transaction will automatically rollback all changes. If the method completes without any exceptions, then the changes will be committed.
Distributed Transaction management
If you have distributed transactions in your method, then we can bind that method under JTA transaction
You may have a scenario where you are altering two data sources and also publishing into a JMS queue. All these operations can be bound under a JTA transaction. The configuration is as below.
<!-- ========== TX MANAGER ============= -->
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManagerName" value="javax.transaction.TransactionManager"/>
</bean>
<tx:advice id="txAdviceJTA" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="persistData*" propagation="REQUIRED" timeout="120000"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="processOperations"
expression="execution(* com.company.proj.bo.process.MyProcess.*(..))" />
<aop:advisor advice-ref="txAdviceJTA"
pointcut-ref="processOperations" />
</aop:config>
Note that if a datasource or a JMS connection factory must be used in a JTA transaction, then they must be XA enabled. Otherwise they will not be involved in a JTA transaction.
Send Text Message To JMS Queue - Spring
Below is a class (the full code infact) that is useful to publish a text mesage into a JMS queue. Note that it use the Spring JmsTemplate. The configuration of which is given after the code snippet.
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;
/**
* Class to send JMS through spring jms template
*
* @author Darel
*
*/
public class JMSSender {
private JmsTemplate jmsTemplate;
public JmsTemplate getJmsTemplate() {
return jmsTemplate;
}
public void setJmsTemplate(JmsTemplate jmsTemplate) {
this.jmsTemplate = jmsTemplate;
}
/**
* send JMS to JMS server
*
* @param queueName
* JMS queue name
* @param msg
* Queue object to be sent
*/
public void sendMesage(String queueName, final String msg) {
jmsTemplate.send(queueName, new MessageCreator() {
public Message createMessage(Session session) throws JMSException {
return session.createTextMessage(msg);
}
});
}
}
Since this now a JMS client, we need the configuration in the application context file of Spring. The configuration is as below.
<!-- ************* JMS Client configuration ************************* -->
<bean id="jmsSender" class="com.mycompany.proj.service.util.JMSSender">
<property name="jmsTemplate">
<ref bean="queueTemplate" />
</property>
</bean>
<bean id="queueTemplate"
class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory">
<ref bean="queueConnectionFactory" />
</property>
<property name="destinationResolver">
<ref bean="jmsDestinationResolver" />
</property>
</bean>
<bean id="queueConnectionFactory"
class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiTemplate">
<ref bean="permissionJndiTemplate" />
</property>
<property name="jndiName">
<value>${jms.connectionFactory}</value>
</property>
</bean>
<bean id="jmsDestinationResolver"
class="org.springframework.jms.support.destination.JndiDestinationResolver">
<property name="jndiTemplate">
<ref bean="permissionJndiTemplate" />
</property>
<property name="cache">
<value>true</value>
</property>
</bean>
<bean id="permissionJndiTemplate"
class="org.springframework.jndi.JndiTemplate">
<property name="environment">
<props>
<prop key="java.naming.factory.initial">
weblogic.jndi.WLInitialContextFactory
</prop>
<prop key="java.naming.provider.url">
${jms.provider.url}
</prop>
<prop key="java.naming.security.authentication">
${jms.authentication.type}
</prop>
<prop key="java.naming.security.principal">
${jms.security.principal}
</prop>
<prop key="java.naming.security.credentials">
${jms.security.credentials}
</prop>
</props>
</property>
</bean>
The values have $(...) which means that the value is got from the properties file. You can also hardcode the values if you do not have a properties file.
The above should be enough to publish a text message to the JMS queue. If you would need to publish a object. You can use the createObjectMessage method instead of createTextMessage.
You may also publidh a SOAP message into the JMS queue using this client. You will have to write the soap message object to a out stream and convert that to a String. The example is shown below.
//Here I am assuming that you have a method to create a SOAP message
SOAPMessage soapMessage = getSoapMessage();
ByteArrayOutputStream out = new ByteArrayOutputStream();
soapMessage.writeTo(out);
jmsSender.sendMesage(queueName, out.toString());
Extract Struct Object from pl/sql output - Spring
For Spring JDBC developers, the first 3 lines show how we can execute and extract the out object. The remaining lines of code is common for any framework.
Map outputMap = simpleJdbcCallObject.execute(map);
String result = (String) outputMap.get("O_RETURN_STATUS");
java.sql.Struct permissionStatus = (java.sql.Struct) outputMap
.get("P_RECORD_STATUS_OBJ");
Now that we have the Struct, let us see a scenario where we have a table object which contains a array of oracle objects. This table object is wrapped inside a wrapper object. (It is better to wrap the table inside a wrapper object which will be recieved as a Struct rather than getting the table object directly from the procedure call)
List recordStatusResult = new ArrayList();
try {
//Get the attributes from the wrapper object (Struct). We may have more than one table/elements inside the wrapper object. but in this case, we have only one table object
Object[] tableAttributeArray = permissionStatus.getAttributes();
//Make sure the the wrapper object is not null or empty
if (tableAttributeArray == null or tableAttributeArray.length < 1
or tableAttributeArray[0] == null) {
throw new Exception(
"No table data found in the response from the pl/sql procedure");
}
//Get the table object from the attributes, which is of type oracle.sql.ARRAY
ARRAY tableAttribute = (ARRAY) tableAttributeArray[0];
//Get the records from the table. Each record will be a object which is required for us.
Object[] structArray = (Object[]) tableAttribute.getArray();
//Make sure the table has atleast one record. You can skip this if not required to check
if (structArray == null or structArray.length < 1) {
throw new Exception(
"No Object data found in the response from the pl/sql procedure");
}
//Loop throw all the records to get the data.
Object struct;
for (int i = 0; i < structArray.length; i++) {
//Get the ith record from the table
struct = structArray[i];
//Each record will have one or more elements or properties
Object[] permissionData = ((Struct) struct).getAttributes();
//In my case, I store it in a list, you can extract the value like String s = (String)permissionData[0];
recordStatusResult.add(permissionData);
}
} catch (SQLException e) {
throw new Exception(
"Error while fetching the data from the pl/sql output.",
e);
}
The code above has comments which explain each step, so I believe no more explanation is required.
I need to return the data in a list, hence I am storing each object array record in the list.
Wednesday, April 21, 2010
Java Split String
But most of the time we may use it incorrectly. One example is, if we have a string with comma separated values, Split can be used to split the comma separated values into String[]. But if we use this directly, most of us might have noticed that it does not consider the last trailing string.
ie., if the string is String str = "a,b,c,d,";
When we do String[] strArray = str.split(","); we get the string array with 4 values only. And not with 5 values. To get the complete string array, we must use as below;
String[] strArray = str.split(",", -1);
This is generally useful when you have multiple rows of such comma separated string or the string is dynamically created.
Tuesday, February 16, 2010
Java IDE
Wednesday, January 13, 2010
My mistake
We were testing our application as its that scary testing phase. (I say scary because, we have a points system where our points are directly related to the number of bugs found while testing.)
And on such a day, our application which till afternoon was working fine, suddenly stopped working. It could not connect to the database!
I checked everything, the code, the connection pool classes, the web server configuration, the data source settings and configuration and even the database.
I and my team broke our heads to resolve the issue. But all was going in vain.
Then after an hour and half, I finally decided to blame it on the database and told the same to the DBA. For which he ofcourse disagreed.
Then the matter was escalated and our development head for help. By looking the at logs he that the .dtd is missing.
Well, there were only 2 xml files, one is web.xml and other is sun-web.xml (sun-web.xml is specific to Sun wevserver). Both were pointing to a internet site of Sun (Example : http://www.sun.com/software/dtd/appserver/sun-web-app_2_5-0.dtd).
I decided to check them on the browser and to my surprise, I found that the Sun's server which hosts these .dtd files has gone down.
Phew, finally found the issue , We then downloaded the .dtd file from some other place and placed it in the config folder of the sun webserver.
All was fine then, the application started working fine.
But due to this lack of knowledge, we wasted about 2 hours, which can be crucial.
So the moral of the story is that any server can go down, so always make sure you place the .dtd or .xsd file which you are referring in your xml on your local server.
Tuesday, January 12, 2010
Avoid namespace in Axis2
This will not work if you want to deploy multiple services or endpoints within the same code (services.xml).
But if you have one service and the services.xml of Axis2 is dedicated for it, then you can achieve the option of not sending /setting the namespace from the client.
For this, simply remove the serviceGroup tag from services.xml and keep only one service in the services.xml file.
Example : If your services.xml file is
<?xml version="1.0" encoding="UTF-8"?>
<serviceGroup>
<service name="MainService" scope="application">
<description>
MainService Service
</description>
<operation name="processSOAPRequest">
<messageReceiver class="org.apache.axis2.receivers.RawXMLINOutMessageReceiver"/>
</operation>
<parameter name="ServiceClass">com.darel.server.service.MainService</parameter>
</service>
</serviceGroup>
Change it by removing the <serviceGroup> and put the service as the main element.
<?xml version="1.0" encoding="UTF-8"?>
<service name="MainService" scope="application">
<description>
MainService Service
</description>
<operation name="processSOAPRequest">
<messageReceiver class="org.apache.axis2.receivers.RawXMLINOutMessageReceiver"/>
</operation>
<parameter name="ServiceClass">com.darel.server.service.MainService</parameter>
</service>
Since there is only one service, Axis2 does not make the namespace mandatory in the Soap request.
Soap Client with XML DOM
String targetERP = "http://myserver.com/NewWebService/services/ServiceName";
SOAPConnectionFactory scf = SOAPConnectionFactory.newInstance();
connection = (SOAPConnection)scf.createConnection();
// SOAPFactory sf = SOAPFactory.newInstance();
// Create the message
MessageFactory mf = MessageFactory.newInstance();
SOAPMessage message = mf.createMessage();
SOAPPart soapPart = ((SOAPMessage) message).getSOAPPart();
SOAPEnvelope envelope = soapPart.getEnvelope();
// message body
SOAPBody body = envelope.getBody();
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
//strRequestXML is a XML string which may be generated by java.
ByteArrayInputStream x = new ByteArrayInputStream(strRequestXML.getBytes());
Document doc = db.parse(x);
body.addDocument(doc); // add the xml to the soap messahe body
((SOAPMessage) message).writeTo(System.out);
System.out.println("\n");
URL endpoint = new URL(targetERP);
SOAPMessage response = connection.call(message, endpoint);
// SOAPMessage response = connection.call("
// endpoint);
connection.close();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
response.writeTo(baos);
responseStr = baos.toString();
Of course the imports are
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import javax.xml.soap.*;
import java.net.URL;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
Monday, January 4, 2010
String builder using variable args
When it comes to appending large amount of strings, it better to use StringBuffer or StringBuilder (StringBuilder is faster since its not synchronized).
Appending to StringBuilder using .append can increase the amount of code and if done in single line can look ugly.
To avoid this, you can use the below method which does it in a clean way by calling a method and passing all string objects as variables.
/**
* Given a continuous string variables, it appends them using a string builder and returns a string.
*
* @param strings - Variable args, a unlimited variables of type String
* @return - A string which is built from the given string variables.
*/
public static String buildString(String... strings) {
StringBuilder sb = new StringBuilder();
for (String str : strings) {
sb.append(str);
}
return sb.toString();
}
//Some method to call the above method by passing variable args
public void someMethod () {
String str = buildString("This", " is", " a", " long string", " which will", " be ", " appended", " to demonstrate", " variable", " args", " feature");
}