I had a table in the UI where one of the column is a LOV. The requirement was to default another field say PayeeName in the same row based on value selected from the LOV only when the PayeeName field does not have a value already.
To accomplish that the simplest idea is to bind the ReturnPopupEvent to a managed bean method and some how get one of the returned value from LOV in this method. Then check if the PayeeName field is null. If yes then update it otherwise just ignore. This can be done quite easily as follows:
1. Go to the ReturnPopupEvent property of the LOV and bind it to a manged bean method.
2. In the bean method you can write the code as below:
public void myLOVListener(ReturnPopupEvent returnPopupEvent) {
List lst= (List)returnPopupEvent.getReturnValue();
String value = null;
for(Object obj : lst{
value = ((Map)obj).get("RoleName"));
if(value != null)
break;
}
//get the iterator binding to get handle of the current row
DCBindingContainer bc =
(DCBindingContainer)BindingUtils.getBindingContext().getCurrentBindingsEntry();
DCIteratorBinding profItr =
bc.findIteratorBinding("MyTableVOIterator");
Row rw = profItr.getViewObject().getCurrentRow();
if(rw != null && rw.getAttribute("PayeeName") == null){
rw.setAttribute("PayeeName", value);
}
}
Please note that I am not really checking for null etc. as my intention is just to show how to use returned values from the returnPopupEvent.
Another important point to note down is that the LOV must have RoleName as one of the return attributes in its configuration in the model.
Showing posts with label ADF UI. Show all posts
Showing posts with label ADF UI. Show all posts
Wednesday, November 21, 2012
Saturday, September 8, 2012
QBE behavior in ADF 11g
QBE - Query By Example is an awesome feature for ADF 11g tables and a number of times can eliminate the need of a search panel completely if you are limited to search the data based on a particular column values.
I am not saying anything new here but just highlighting this feature. So, also does not make sense to copy paste from dev guide etc. All the supported operators are well documented at http://docs.oracle.com/cd/E23943_01/web.1111/e16272/databound_forms.htm#ADFJE389
For those who want to change the QBE behavior for more advance requirements then there is a way to do that well. There is an excellent whitepaper on that and can be followed at https://docs.google.com/viewer?url=http%3A%2F%2Fwww.oracle.com%2Ftechnetwork%2Fdeveloper-tools%2Fadf%2Flearnmore%2F30-table-filter-queries-169172.pdf
I am not saying anything new here but just highlighting this feature. So, also does not make sense to copy paste from dev guide etc. All the supported operators are well documented at http://docs.oracle.com/cd/E23943_01/web.1111/e16272/databound_forms.htm#ADFJE389
For those who want to change the QBE behavior for more advance requirements then there is a way to do that well. There is an excellent whitepaper on that and can be followed at https://docs.google.com/viewer?url=http%3A%2F%2Fwww.oracle.com%2Ftechnetwork%2Fdeveloper-tools%2Fadf%2Flearnmore%2F30-table-filter-queries-169172.pdf
Tuesday, March 20, 2012
ADF Library to ViewController or to Model ?
Very recently, I got into the conversation when a friend got an external ADF library which had BC4J components and he wanted to use it to create a UI page.
As the library contained BC4J components or you can say an ApplicationModule so the first thought came was to add the library to the model project and expecting the AM bundled in the library to show up in Data Controls. And when it was tried this way the AM did not show up in the Data controls. Refreshing, rebuilding the project or even restart did not help.
So, trying to dig further when we opened the model project properties and went to the business components import we could see the EOs,VOs and AM from the ADF library.
After a while, when the library was added to the ViewController project and the ApplicationModule immediately showed up in the data controls. So, what was actually happening here?
Well, that's actually how it should have been done. As a thumb rule, if you want to build UI based on the ADF library jar provided by third party then it should be a dependency of UI project not the model project. However, if you want to extend your BC4J components based on the provided components in the adf library then you should add it to the model project.
As the library contained BC4J components or you can say an ApplicationModule so the first thought came was to add the library to the model project and expecting the AM bundled in the library to show up in Data Controls. And when it was tried this way the AM did not show up in the Data controls. Refreshing, rebuilding the project or even restart did not help.
So, trying to dig further when we opened the model project properties and went to the business components import we could see the EOs,VOs and AM from the ADF library.
After a while, when the library was added to the ViewController project and the ApplicationModule immediately showed up in the data controls. So, what was actually happening here?
Well, that's actually how it should have been done. As a thumb rule, if you want to build UI based on the ADF library jar provided by third party then it should be a dependency of UI project not the model project. However, if you want to extend your BC4J components based on the provided components in the adf library then you should add it to the model project.
Friday, December 30, 2011
Redirecting to a URL from a action listener in ADF 11g
Very recently I was working on implementing OaAuth flow a typical standard these days to authenticate against various social media sties like facebook, twitter etc. using my ADF app. Typically, the first step in OaAuth flow is to call to a url passing some sort of consumer or app id which then in turns after authentication invokes a callback url from your application to do the next step.
As a result, I needed a way to invoke the url from adf command button actionListener after constructing the url containing user supplied consumerKey. So, here is how you can invoke the url after that:
FacesContext fc = FacesContext.getCurrentInstance();
String callbackUrl = "https://www.facebook.com/dialog/oauth?client_id=" + fbClientId.getValue().toString() + "&redirect_uri=http://mycallbackURL&scope=publish_stream";
try
{
fc.getExternalContext().redirect(callbackUrl);
}
catch (IOException e)
{
e.printStackTrace();
}
So this is written in the actionListener and fbClientId is bound to a inputText on UI.
As a result, I needed a way to invoke the url from adf command button actionListener after constructing the url containing user supplied consumerKey. So, here is how you can invoke the url after that:
FacesContext fc = FacesContext.getCurrentInstance();
String callbackUrl = "https://www.facebook.com/dialog/oauth?client_id=" + fbClientId.getValue().toString() + "&redirect_uri=http://mycallbackURL&scope=publish_stream";
try
{
fc.getExternalContext().redirect(callbackUrl);
}
catch (IOException e)
{
e.printStackTrace();
}
So this is written in the actionListener and fbClientId is bound to a inputText on UI.
Friday, April 1, 2011
Design standard for Page Templates and Sub flow calls in ADF 11g
ADF 11g provides effective ways of reducing development time by allowing to create page templates. So, a typical example of a page template is say your organization app has 20 pages and all having same header and footer (or at least same layout). Now to avoid repetition of designing or copy/pasting manually in all 20 pages you can simply create a page template and use it while creating your pages. Page templates are much more powerful than what I said above as it allows you to keep dynamic data as well. But lets understand it this way as the focus of this post is different.
When a Task Flow call is made from a page fragment which is inside a task flow then the Task flow being invoked is referred as Sub-flow. The design diagram usually represent it by a task flow call activity. So, one of the most typical mistake I have seen people doing in ADF world is wrong way of using the page template in the sub-flows. You must make sure that if the parent flow is using a page Template A (say with two areas header and content area) then you must not use the same template Template A in any of the page fragment of the sub-flow. The simple obvious reason is: in that case the page fragment of subflow having Template A will be embedded in the parent task flow page fragment. Visually, It is like:
When a Task Flow call is made from a page fragment which is inside a task flow then the Task flow being invoked is referred as Sub-flow. The design diagram usually represent it by a task flow call activity. So, one of the most typical mistake I have seen people doing in ADF world is wrong way of using the page template in the sub-flows. You must make sure that if the parent flow is using a page Template A (say with two areas header and content area) then you must not use the same template Template A in any of the page fragment of the sub-flow. The simple obvious reason is: in that case the page fragment of subflow having Template A will be embedded in the parent task flow page fragment. Visually, It is like:
So, what is the right way? Well in case of sub flows you should only use the page Template A in the parent task flow's page fragments. For the subflow page fragments there should not be any page template or there should be another page Template B which is only the content part of the template A.
So, in other words you should create a page Template B with only content part of Template A. And then you should create another Template A with only header part and in the content area including template B. So, for parent flow page fragments Template A should be used and for sub flow page fragment's Template B should be used.
Labels:
ADF Declarative components,
ADF UI,
Page Template,
Task Flow
Friday, February 18, 2011
Controlling message display style in ADF 11g
On a ADF form bound to a updatable view object, when you hit the submit button after entering data, error messages may come up due to failures like required attribute missing, business logic failure written at EO level etc.
By default these messages comes as a popup on the screen. If you want them to appear on the page itself instead of a popup then there is a property 'inline' in the af:messages component. You can set it to true.
However, there may be a requirement where you want to display the messages as inline if it's just one message and in a popup if more than one. To do that, you can create a request scope managed bean and count the size of iterator FacesContext.getCurrentInstance().getMessages(); Based on the count you can return true or false.
A sample workspace can be found here
By default these messages comes as a popup on the screen. If you want them to appear on the page itself instead of a popup then there is a property 'inline' in the af:messages component. You can set it to true.
However, there may be a requirement where you want to display the messages as inline if it's just one message and in a popup if more than one. To do that, you can create a request scope managed bean and count the size of iterator FacesContext.getCurrentInstance().getMessages(); Based on the count you can return true or false.
A sample workspace can be found here
Thursday, February 3, 2011
javax.net.ssl.SSLKeyException due to certificate check by wls in ADF 11g
I was working on some facebook apis for trying http://www.restfb.com java framework and got below exceptions from wls:
If you are getting this error then you can set the wls to ignore the certificate check by adding
-Dweblogic.security.SSL.ignoreHostnameVerification=true
to your run/debug/profile of view controller properties. It should work.
javax.net.ssl.SSLKeyException: [Security:090504]Certificate chain received from ****** graph.facebook.com failed hostname verification check. Certificate contained *.facebook.com but check expected graph.facebook.com
If you are getting this error then you can set the wls to ignore the certificate check by adding
-Dweblogic.security.SSL.ignoreHostnameVerification=true
to your run/debug/profile of view controller properties. It should work.
Friday, February 26, 2010
A note about multiple regions in ADF 11g
Well, most of ADF applications have a need to have multiple regions on a single page. For each task flow dropped on the ADF page, an entry is created in the page definition file.
At the run time, ADF framework looks into the page definition file and tries to execute/initialize all the regions. This is true even if you set the render property of the region in your page to false. At times, this may not be desirable and in some cases this could create problems for you. Every region entry in the page definition comes up with a property called activation which has default value 'immediate'. So, to avoid un-necessary execution of a region you can change it to value 'conditional'. And in the next to it property active you can specify an EL expression with a condition to execute that region.
Tuesday, February 16, 2010
A point about Bookmarking view activity in ADF 11g
Few days back I was trying to implement bookmarkeble view activities in my ADF 11g app. This is quite easy. The only condition is you can bookmark only the view activities of a unbounded task flow. To enable bookmarking you can select the bookmark property to true of a view activity in the unbounded task flow.
One interesting thing to note down is the method attribute with the bookmark property. You create a method in a managed bean and provide it here. So, whenever someone pastes/types the url in browser this method gets invoked giving you the opportunity to update model or set values in pageflowScope or other scopes which might be needed to render the page properly.
But, remember this method will not be called when user navigates to this page from another page by adfc navigation. So, a question arises is how to do the required stuff in such cases which happens through the initializer method. What you can do to resolve this is you can expose your bean as a Data Control and then drop the same method in the unbounded task flow as a method activity. So, instead of directly navigating to the bookmarked activity, navigate to the method call. This way it will be ensured that desired page parameters/model is initialized.
Monday, January 25, 2010
Updating a Declarative Component in ADF 11g
I have an adf application built in 11g. It uses a custom component which I developed for this app. Recently, I got the need to add an additional attribute in the component. So, thought to blog about the way I did it without any issues.
1. Open your custom declarative component project.
2. Add the desired attribute and deploy it to your desired path.
3. Stop the running wls.
4. Open your main project ( consumer project). Go to properties.
5. From the libraries and class path delete the existing entry for the component.
6. Save the changes and close the jdeveloper completely.
7. Open the consumer project's ViewController.jpr in a text editor.
8. Remove all the entries containing any reference to your custom component library.
9. Reopen jdev and then go to resource pallet.
10. Add a file system connection and select the directory you generated the custom declarative component jar.
That's it. It should reflect your new updates component nicely.
Friday, September 4, 2009
Fine tuning your Application Module in ADF
A lot of performance problems occurs because we leave the stuff as soon as it starts working as per the requirement.
We simply do not care at the performance issues it might create later in system testing or in production environment with real life load. I am not a guru of performance tuning but here is a little I learned from my experience:
1. Carefully investigate each and every instance you added in the Application module of your view objects. Go to the tunning tab and there set the batch size equals to 1 more than the expected number of rows the VO is expected to get from DB in most of the cases. This will save un-necessary db trips. So, say a VO instance is supposed to retrieve 20 rows from db then change the batch size to 21. By default it is 1.
2. Iterating over View objects is fairly easy and we easily get into the habit of copy paste the code to iterate over the View object to look for some particular row. Avoid that. Where ever possible have a view criteria with bind variables and use it in your code to fetch the desired row.
3. One of the most neglected area is executables sometimes left in pageDefinition files and un-used. During development we try various options when something does not work and forget to remove the dead code. So, those executables run at the page load time automatically and slow down the page load time even it does not have any other side-effects on the functionality of your app. So, remember to analyze bindings and executable in your pageDef and cleanup if needed.
Wednesday, August 19, 2009
Using Parent Action in ADF 11g
What is parent action and why do we need it?
Imagine a situation where you are on a page and you have dropped a bounded task flow A as a region on this page. Now, from inside TF A you call another bounded task flow TF B which in turns calling a third bounded TF C. So, finally you are in task flow TF C.
Here, there are situations when you may need to navigate directly to TF-A rather to TF-B. So, in this case a task flow return activity will not help as it will only navigate you to the taskflow TF-B.
So, to go to TF-A you can drop a parent Action activity from component pallet on the task flow diagram of TF-C. In the properties define the parent outcome as some string value.
Now in the TF-B drop a task flow return activity and have a control flow navigation from your view activity to the task flow return activity. Give the name to this rule same as the parent outcome name you provided in TF-C.
That's it. Now, when you will will navigate from TF-C the flow will directly navigate to TF-A and skipping the TF-B.
Saturday, August 15, 2009
Programmatic error message in ADF 11g
There are situations when you may need to show error messages from your managed bean code to your ADF page.
ADF 11g supports two level of messages as component level messages and page level messages.
To show a component level message write the following code in your managed bean method:
FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_ERROR, null, "this is component level message");
FacesContext context = FacesContext.getCurrentInstance();
context.addMessage(.getClientId(context), msg);
Also note that you need to add component to your page (anywhere).
To show page level message you need to use following code:
FacesContext context = FacesContext.getCurrentInstance();
FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_ERROR, null, "Value entered is invalid");
context.addMessage(null, msg);
Monday, August 10, 2009
What is DataBindings.cpx file in ADF
In ADF web application as soon as you drop a databound component on your page a DataBindigs.cpx file gets created. You may wonder what it contains? So, here it is:
Till Jdeveloper 10.1.3 release there was no way to have multiple DataBindings.cpx files which was very much desirable as the project the size grows and being worked by multiple teams. Jdeveloper 11g added this capability to have multiple DataBindings.cpx files.
There is a new file in 11g called adfm.xml which is used to keep track of all such DataBindings.cpx files in the system. So, when breaking your DataBindings.cpx you need to take care to create an entry in the adfm.xml. You also need to make sure to have unique application id attribute value for each entry.
Friday, July 24, 2009
Moving components towards right in ADF
So, I got a small requirement to render a help commandlink on page towards top right corner.
And here is the trick applied:
<af:panelGroupLayout id="pgl3" layout="horizontal">
<af:toolbar id="t1" flex="1" stretchId="spacer1">
<af:spacer width="10" height="10" id="spacer1"
clientComponent="true"/>
<af:commandLink text="login" id="cl3"/>
<af:spacer width="10" height="10" id="s2"/>
<af:goLink text="FAQ" id="gl1"/>
</af:toolbar>
</af:panelGroupLayout>
Tuesday, April 28, 2009
Avoiding Back button mishaps in ADF
In ADF, it is pretty difficult to handle browser back button press. This may lead to unwanted/ unexpected behavior. In simplest wordings the reason could be out of sync iterators when browser back button is pressed. There are no good solutions to avoid it but you can certainly prevent the side effects in a graceful manner. There can be two ways to do so:
1. Set the screen size so that back button is not visible at all.
2. Use enableTokenValidation in the application.
2nd solution deserves some attention and more details. Basically when you enable token validation for a page, that page is annonated with the current object for all the iterators used to render that page. This annonation is then submitted to the server along with any data. At that point the current object of the iterator is compared with the annotation. If they differs and exception is thrown. So, this way user can be prevented to see weired behavior.
To enable just go to page definition file and set the tokenValidation property to true.
Wednesday, February 11, 2009
Page load performance of query page in ADF
Well, a considerable number of the pages in an application is likely to have query components. By default in ADF when the page renders it executes all the iterators and executables (in other words data control bindings which would be bound to VOs or other AM or managed bean methods).
So, that means associated queires to VOs will be executed and depending upon the size of underlying table, connectvity with the db, indexing of table etc it may take a considerable time for the page to come up for the first time.
Even it is the intension of the end user to open a qeury page for querying the data but still running the app and waiting for the page to come up for more than 15-20 sec may be quite annoying. So, a better idea from user experience perspective is to show the page as soon as possible and then let user query it himself by hitting a search button.
If this makes sense to you then the solution for the same is to create a view criteria and unchek the query automatically check box. This will prevent the initial SQL execution.
A more details explaination is provided by Andrejus at his blog with a sample app.
Saturday, December 15, 2007
Phases in ADF Lifecycle
ADF Lifecycle has 9 phases as follows:
- Initialize Context: In this phase value of associated request, binding container and lifecycle are set.
- Prepare Model: In this phase model is prepared and initialized. In this phase page parameters are set and methods in the executable section of the page definition of the ADF page are executed.
- Apply Input Values: This phase handles the request parameters. These may come by adding the request parameters in the URL or as a result of HTML form post action. A list of events is build using these request parameters. These are actually mapped to the bindings in the pageDef. This phase resolves the list of request parameters bind to attributes in the page def. This results into a list of updatedValues which is used in Update Model phase.
- Validate Input Values: This phase validates the list of values built in the Apply input values field with model.
- Update Model: After validation of input values data model is updated with the list of values created in apply input values phase.
- Validate Model Updates: Updates in the previous phase are then validated by calling the associated lifecycle's binding container .
- Process Component Updates: This phase handles any events in the list built during the apply input values phase. Named events as well as all the events tied to the databindings created in the pagdef are handled in this phase.
- Metadata Commit: This phase commits the runtime metadata changes to the model. This phase is always executed before prepare render phase.
- Prepare Render: This is the final phase where page is sent for rendering. prepareRender event is sent to all the registered listeners. This notification signals bindings to prepare or process the data for rendering. Binding container is also refreshed. This helps in updating any changes that happened during validation phases.
Thursday, October 4, 2007
Creating List Based ADF Table for data capturing
Sometimes, there is a requirement to capture a set of user inputs which is needed for some temporary computations. For example say details of some employees is required and it is not based on any database table. In that case, many a time creating a temporary table in the database is not always desired. To avoid that you can base your ADF table component on a java.util.List object (other objects too possible in the similar manner).
STEP By STEP: Find Live Demo Here
STEP By STEP: Find Live Demo Here
- Say we have to create a table component to capture emp code, emp name and dept of n number of employees. To do this, just drop a ADF table component on your jspx(or jsp ) page. and create three columns. Give headers as EmpCode, EmpName, Dept. for values property type as: #{row.empCode}, #{row.empName} and #{row.dept}, respectively. Also change the column types from outputText to inputText.
- Now, you need a List object to bind this ADF table. Ofcourse, this List will be containing items as another java objects to hold value for each of the table column. Go to the table properties in the property inspector and choose value property. Click on the button on the right side. It will ask for the managed bean name. Give bean name as DemoBean. package say vik.demo.ui.bean, bean scoipe as session, and property name as entryList.
- This will create a manged bean named DemoBean with default constructor. Now declare a private class member as Private List entryList. Now right click on it and choose generate accessors.
- Now, you will need a another java class which can initialize the rows of table and can populate the entryList created above. For this create a new java class and declare three members as public String empCode; public String empName; public String dept; Finally, right click and choose generate accessors.
- Now, come back to DemoBean.java and in the constructor write code as: if(entryList == null){ entryList = new ArrayList(); }.
- Go to your jspx file and click on the table component created in step 1 in the structure window. In the var property enter row.
- Now, you need a button to add blank rows into the table. For this drag and drop a button to the table header. Name it as Add Row. Click on the button right side of actionListener property. Choose manage bean name as DemoBean. and give a method name say OnClickAddRowBtn.
- Go to DemoBean.java and in the OnClickAddRowBtn add following code: EmpBean e;
e = new EmpBean();
e.setEmpCode("");
e.setEmpName("");
e.setDept("");
list.add(e);
return; - Congratulations!!!! You are done.
Monday, September 24, 2007
Achieving show/hide of components using single list choice component
Hie
This post depicts how to achieve show hide behavior of ADF UI components on the basis of list element selection.
Following steps need to be followed:
1. Enabling PPR: You will need to enable Partial page rendering so that page cannot be refreshed while changing the element selection in the list box.
2. Need to set the visible property of the component to be hide/show depending upon the element selected in the list box.
Let's proceed using an example: say there is a list box having values Package and Procedure. And there is a text box. Now we want this text box to be visible on the page when package is selected in the list box. and in case procedure is selected it should be hidden.
Detailed Steps:
1. Create a ADF project in Jdeveloper (Make sure ADF libraries are included in the project).
2. Create A new page or use an existing page.
3. Drag and drop Select One Choice component from the ADF Faces components at the desired place on the page. Fill the details in the wizard for list name-value pairs.
3.2 Click next and give desired label.
3.3 Change auto submit property to true and click finish.
3.4 Give list component an id ListDemoId. This will be used for setting PPR.
3.5 Also set the value property to 'Package'. This will be used as default selection for the list Choice.
3.6 We need to create a binding for using list values to decide the show hide of text box at run time. To create that go to the behavior tab of list box properties.
3.7 click on the bindings arrow and a new pop up window will appear asking name for the managed bean and property.
3.8 Click new and give name as DemoBean and same way for property give name ListRendering.
4. Now create an input text box by dragging and dropping a inputText component from the ADF components.
4. Go to it's behavior tab and click on the arrow right to partial rendering. it will pop up a wizard asking for available items for PPR.
4.1 shuttle selectListChoice-ListDemo towards right and click ok.
5. Final step is setting up the visible property of the text box using an EL expression.
5.1 For this go to the behavior tab of the text box and set visible property as: #{DemoBean.listRendering.value=='Package'}
5.2 That's it. The above EL expression will set the visible property to true only if selected value in the list is Package. In other cases text box will be hidden.
For doubts/ suggestions or comments please mail me अत: vik.ceo@gmail.com
This post depicts how to achieve show hide behavior of ADF UI components on the basis of list element selection.
Following steps need to be followed:
1. Enabling PPR: You will need to enable Partial page rendering so that page cannot be refreshed while changing the element selection in the list box.
2. Need to set the visible property of the component to be hide/show depending upon the element selected in the list box.
Let's proceed using an example: say there is a list box having values Package and Procedure. And there is a text box. Now we want this text box to be visible on the page when package is selected in the list box. and in case procedure is selected it should be hidden.
Detailed Steps:
1. Create a ADF project in Jdeveloper (Make sure ADF libraries are included in the project).
2. Create A new page or use an existing page.
3. Drag and drop Select One Choice component from the ADF Faces components at the desired place on the page. Fill the details in the wizard for list name-value pairs.
3.2 Click next and give desired label.
3.3 Change auto submit property to true and click finish.
3.4 Give list component an id ListDemoId. This will be used for setting PPR.
3.5 Also set the value property to 'Package'. This will be used as default selection for the list Choice.
3.6 We need to create a binding for using list values to decide the show hide of text box at run time. To create that go to the behavior tab of list box properties.
3.7 click on the bindings arrow and a new pop up window will appear asking name for the managed bean and property.
3.8 Click new and give name as DemoBean and same way for property give name ListRendering.
4. Now create an input text box by dragging and dropping a inputText component from the ADF components.
4. Go to it's behavior tab and click on the arrow right to partial rendering. it will pop up a wizard asking for available items for PPR.
4.1 shuttle selectListChoice-ListDemo towards right and click ok.
5. Final step is setting up the visible property of the text box using an EL expression.
5.1 For this go to the behavior tab of the text box and set visible property as: #{DemoBean.listRendering.value=='Package'}
5.2 That's it. The above EL expression will set the visible property to true only if selected value in the list is Package. In other cases text box will be hidden.
For doubts/ suggestions or comments please mail me अत: vik.ceo@gmail.com
Subscribe to:
Posts (Atom)
