Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Events
Videos
Audiobooks
Packt Hub
Free Learning
Arrow right icon
timer SALE ENDS IN
0 Days
:
00 Hours
:
00 Minutes
:
00 Seconds

How-To Tutorials

7019 Articles
article-image-version-management-upk-35-part-1
Packt
18 Nov 2009
12 min read
Save for later

Version Management with UPK 3.5: Part 1

Packt
18 Nov 2009
12 min read
Though this implies multi-user environment client/server setup, this article could still be of use to a single user. If you are a lone developer working in a stand-alone environment (sometimes referred to as a single-user environment), you may want to just skip straight to the section Exporting and importing content, at the end of this article. Sorry, but version management in a stand-alone environment is almost nonexistent, so you're largely on your own. In a client/server environment, all content objects are stored on a central server. Developers create and edit content on their own workstation (typically, a PC), and then send this content to a database on the central server for storage when they have finished with it. This is referred to as checking in a content object. If they subsequently want to change a content object that is on the server, they need to check out the content object, change it, and then check it back in. Checking in a content object When you create a content object in a client/server environment, the content object is stored in your local repository (that is, on the client side of the client/server set-up). It is important to understand that your local repository is located on your local workstation. This means that if you have a content object in your local repository, and then move to another workstation, you will not see your local version of this content object on the new workstation. This is because they are located on the workstation on which you created them or checked them out. You should, therefore, always use the same workstation, or check in all of your content objects before moving to a new workstation. Version Difference In UPK 3.5.1 client/server edition, it is possible to choose a specific location for your local repository. Documents that are checked out to your local repository are identified by a green check-mark () to the left of the object details in the Library. You will need to check these objects in if you want them to be stored in the central library so they will be accessible to other developers. Generally, you should do this whenever you have finished working on a content object. However, there are good reasons for checking in objects more frequently. Your local repository is (typically) in the C: drive of your computer, whereas the central library is on a central server. Unless you are very fastidious about backing up your PC, the chances are that the server is backed up more often than your PC, and is usually backed up automatically. Placing your files in the central library will, therefore, provide greater security in terms of having a backup that you can revert to in the event of a disaster. In addition to this, whenever you check in a content object, a back-up of the previous version is taken. Should you need to back out your changes, you can simply revert to a back-up version of the content object. Related to this, content objects are never really deleted from the server library. Actually, they can be deleted, but only by an Administrator, and typically as part of a cleanup. However, if you delete an object (that has never been checked in to the central server) from your local repository, it is gone. Forever! Finally, it is not unheard of that for a local repository to simply disappear when UPK crashes (which thankfully doesn't happen that often, but it has been known to happen). If this does happen to you, then you may again lose all of the content that you currently have checked out even though the Lost documents view, introduced in UPK 3.5, may save you. For all of these reasons, and more, it is recommended that you check in all of the content that is stored in your local repository to the server at least at the end of each work day. Fortunately, UPK provides a quick way of doing this. If you click the Check In All button (), then all of the content objects that you currently have checked out will be checked in again. The downside of this is that if you do check in all of the documents that you are currently working on at the end of the day, you need to remember which objects these are so that you can check them out again the next day. Before checking the documents in, you can use the All Checked Out view to identify all of the documents that you currently have checked out. You can then print this list, and will know which files you need to check out again the next day. There is one further thing to bear in mind when deciding how often you are going to check in your content objects. As mentioned above, UPK retains every version of a content that has been checked in. These are accessible via the Document History, as we will see later. This means that if you check in a content object ten times, then UPK will retain ten versions of it. This can also make it difficult to identify the "released" versions of the content object in the Document History, should you subsequently need to roll back to the last-released version. The Administrator can clean up the database periodically, but the only way of differentiating between versioned drafts and versioned released content objects is by using the check-in comments. These are explained in the section Step 2: Confirming the scope of the check-in, ahead. UPK Workflow The down-side to checking in content objects that you may not have finished working on is that these objects could be mis-perceived as being final, and actually used or published. In some cases, you may have checked some of them out not because you want to change them, but specifically to stop anyone else from changing them. UPK does not provide you with a simple, automatic solution to these shortcomings, but there is a manual workaround that you can use. This is to use the Workflow properties of the objects. UPK does not have a "real" workflow, it does not have "routing" of content objects based on their status, or automatic email notification of content objects that are due for review, or any of the things that you'd expect from workflow functionality. Of course users can use a Custom View to display content objects according to their Workflow properties, but this is still not really "workflow". If you display the Properties for a content object, by clicking on the Properties button (), the last category of properties (at the end of the Properties list) is the Workflow category. Within this, there are two properties: Assigned To: This property allows you to select (from a drop-down list) the user ID of any developer who is authorized to access the content object. If you select your own user ID in this field, then this is an indication to the other developers that they should not work on this content object themselves. State: This property can be used to specify the current status of a content object. The default values are (blank), Not Started, Draft, In Review, and Final. If you select a status of Draft, then this is an indication that you have not finished with the object. An example of the Properties pane, showing the Workflow category, is in the following screenshot: Of course, these Properties do not provide "hard checks". You still rely on people seeing, and then paying attention to, these Properties but it is a start. Some strong guidance from your Project Manager or Team Leader would help, here. Administrators can add, remove, or rename statuses. It is recommended that a suitable set of statuses are established and defined prior to any development work starting, and that these statuses and their meaning are communicated to all developers. Step 1: Selecting the content objects To select content objects for checking in, carry out the steps described below: In the Library, select the content objects that you want to check in. Normally, you will want to check in specific content objects, so you would only select these. As always, you click on a content object to select it. You can Ctrl-click to select multiple objects, or Shift-click to select a series of objects. You can also select a folder in the Library to check in all of the content objects in that folder. Do not worry if some of the objects that are included in your selection are not checked out; UPK will simply ignore these objects during check-in (it will not "error-out"). You can also check in content objects by selecting them in an Outline Element. Once you have selected all of the content objects that you want to check in, click the Check In button (). Step 2: Confirming the scope of the check-in When you check in one or more specific content objects and have not selected a folder in the Library to check in, UPK will automatically check for any content objects that are related to the object that you are checking in. You will be prompted to check these in, as well. An example of this prompt is shown in the following screenshot: What are related documents? In general, they are Web Pages and Packages used by the content object that you are checking in. For example: If you have created, or changed, an Outline Element (Module or Section) then UPK will automatically select all of the content objects (Modules, Lessons, and Topics) included in this Outline Element, along with any Web Pages used by any of these content objects will be selected as well. If you are checking in a Topic for which you have also created a Web Page that is linked to the Concepts pane, then UPK will automatically select this Web Page as a related document during check-in. If you have created a Glossary and are checking this in, the related documents will be all of the Web Pages for the content definitions used by the Glossary. An example of the Related Documents dialog box is shown in the following screenshot: This list of related documents can be confusing, as it does not match the list that you see in the Related Documents pane for the content object that you are checking in. The list will include Web Pages that have been explicitly linked to from the content object that you are checking, but will not include Glossaries, Templates, Packages, or icons, all of which are listed in the Related Documents pane. This limitation is important to understand. If you checked out, and updated, a Glossary and as a result UPK checked out several content objects during the glossary link regeneration, UPK will not identify all of the Glossary elements (Web Pages) as "related documents". You will need to locate these and check them in separately using custom views. Adding to the confusion, the list of related documents will include all of the explicitly-linked content objects, and not only those objects that are currently checked out to you. Do not worry about this – UPK will only physically check in the content objects that you do have checked out (these are the ones that have your User ID in the Checked Out To column of the Related Documents dialog box). In the example above, we are checking in an Outline Element that contains one Topic, which in turn contains a Web Page. The Topic is checked out, but the Web Page is not. Step 3: Specifying a reason for the change When you check in one or more content objects, you will be prompted to enter a short description of the change, as shown in the following screenshot: The title of the content object that you are checking in is shown above the comment text box. If you are checking in multiple content objects, then this will initially specify the first object and (assuming that you deselect the Apply same comments to all documents option) will specify the next object when you click OK. It then moves on to the next object. In this way, you can provide separate comments for each content object, even though you are checking them all in at the same time. Although it is entirely optional, it is a very good practice to always enter a comment. This comment will appear in the object History, and therefore serves as a useful audit log. Specify a reason for the change by carrying out the steps described below: Enter a suitable comment in the space provided. Note that you do not need to specify the date and time of the change, or your user ID. These are captured automatically by UPK. If you are checking in multiple objects, then UPK will initially suggest applying the same comments to all of the objects. You will see that the Apply same comments to all documents checkbox toward the bottom of the dialog box is selected. If you do not want to use the same comment for all objects that you are checking in, then deselect this checkbox. Now, when you click on OK, the Check In Comment dialog box for the next content object in the check-in selection will be displayed. You can repeat the above processing from Step 3. Once you have entered your description of the change (or reason for the change), click on OK. Once you have specified a comment, and clicked on OK for the last time, then the content objects are checked in, and the Library screen is redisplayed.  
Read more
  • 0
  • 0
  • 1800

article-image-integrating-websphere-extreme-scale-data-grid-relational-database-part-1
Packt
18 Nov 2009
10 min read
Save for later

Integrating Websphere eXtreme Scale Data Grid with Relational Database: Part 1

Packt
18 Nov 2009
10 min read
As stated above there are three compelling reasons to integrate with a database backend. First, reporting tools do not have good data grid integration. Using CrystalReports and other reporting tools, don't work with data grids right now. Loading data from a data grid into a data warehouse with existing tools isn't possible either. The second reason we want to use a database with a data grid is when we have an extremely large data set. A data grid stores data in memory. Though much cheaper than in the past, system memory is still much more expensive than a typical magnetic hard disk. When dealing with extremely large data sets, we want to structure our data so that the most frequently used data is in the cache and less frequently used data is on the disk. The third compelling reason to use a database with a data grid is that our application may need to work with legacy applications that have been using relational databases for years. Our application may need to provide more data to them, or operate on data already in the legacy database in order to stay ahead of a processing load.In this article, we will explore some of the good and not-so-good uses of an in-memory data grid. We'll also look at integrating Websphere eXtreme Scale with relational databases. You're going where? Somewhere along the way, we all learned that software consists of algorithms and data. CPUs load instructions from our compiled algorithms, and those instructions operate on bits representing our data. The closer our data lives to the CPU, the faster our algorithm can use it. On the x86 CPU, the registers are the closest we can store data to the instructions executed by the CPU. CPU registers are also the smallest and most expensive data storage location. The amount of data storable in registers is fixed because the number and size of CPU registers is fixed. Typically, we don't directly interact with registers because their correct usage is important to our application performance. We let the compiler writers handle translating our algorithms into machine code. The machine code knows better than we do, and will use register storage far more effectively than we will most of the time. Less expensive, and about an order of magnitude slower, we have the Level 1 cache on a CPU (see below). The Level 1 cache holds significantly more data than the combined storage capacity of the CPU registers. Reading data from the Level 1 cache, and copying it to a register, is still very fast. The Level 1 cache on my laptop has two 32K instruction caches, and two 32K data caches. Still less expensive, and another order of magnitude slower, is the Level 2 cache. The Level 2 cache is typically much larger than Level 1 cache. I have 4MB of the Level 2 cache on my laptop. It still won't fit the contents of the Library of Congress into that 4MB, but that 4MB isn't a bad amount of data to keep near the CPU. Up another level, we come to the main system memory. Consumer level PCs come with 4GB RAM. A low-end server won't have any less than 8GB. At this point, we can safely store a large chunk of data, if not all of the data, used by an application. Once the application exits, its data is unloaded from the main memory, and all of the data is lost. In fact, once our data is evicted from any storage at or below this level, it is lost. Our data is ephemeral unless it is put onto some secondary storage. The unit of measurement for accessing data in a register, either Level 1 or 2 cache and main memory, is a nanosecond. Getting to secondary storage, we jump up an SI-prefix to a microsecond. Accessing data in the secondary storage cache is on the order of microseconds. If the data is not in cache, the access time is on the order of milliseconds. Accessing data on a hard drive platter is one million times slower than accessing that same data in main memory, and one billion times slower than accessing that data in a register. However, secondary storage is very cheap and holds millions of times more than primary storage. Data stored in secondary storage is durable. It doesn't disappear when the computer is reset after a crash. Our operation teams comfortably build secondary storage silos to store petabytes of data. We typically build our applications so the application server interacts with some relational database management system that sits in front of that storage silo. The network hop to communicate with the RDBMS is in the order of microseconds on a fast network, and milliseconds otherwise. Sharing data between applications has been done with the disk + network + database approach for a long time. It's become the traditional way to build applications. Load balancer in front, application servers or batch processes constantly communicating with a database to store data for the next process that needs it. As we see with computer architecture, we insert data where it fits. We squeeze it as close to the CPU as possible for better performance. If a data segment doesn't fit in one level, keep squeezing what fits into each higher storage level. That leaves us with a lot of unused memory and disk space in an application deployment. Storing data in the memory is preferable to storing it on a hard drive. Memory segmentation in a deployment has made it difficult to store useful amounts of data at a few milliseconds distance. We just use a massive, but slow, database instead. Where does an IMDG fit? We've used ObjectGrid to store all of our data so far. This diagram should look pretty familiar by now: Because we're only using the ObjectGrid APIs, our data is stored in-memory. It is not persisted to disk. If our ObjectGrid servers crash, then our data is in jeopardy (we haven't covered replication yet). One way to get our data into a persistent store is to mark up our classes with some ORM framework like JPA. We can use the JPA API to persist, update, and remove our objects from a database after we perform the same operations on them using the ObjectMap or Entity APIs. The onus is on the application developer to keep both cache and database in sync: If you take this approach, then all of the effort would be for naught. Websphere eXtreme Scale provides functionality to integrate with an ORM framework, or any data store, through Loaders. A Loader is a BackingMap plugin that tells ObjectGrid how to transform an object into the desired output form. Typically, we'll use a Loader with an ORM specification like JPA. Websphere eXtreme Scale comes with a few different Loaders out of the box, but we can always write our own. A Loader works in the background, transforming operations on objects into some output, whether it's file output or SQL queries. A Loader plugs into a BackingMap in an ObjectGrid server instance, or in a local ObjectGrid instance. A Loader does not plug into a client-side BackingMap, though we can override Loader settings on a client-side BackingMap. While the Loader runs in the background, we interact with an ObjectGrid instance. We use the ObjectMap API for objects with zero or simple relationships, and the Entity API for objects with more complex relationships. The Loader handles all of the details in transforming an object into something that can integrate with external data stores: Why is storing our data in a database so important? Haven't we seen how much faster Websphere eXtreme Scale is than an RDBMS? Shouldn't all of our data be stored in in-memory? An in-memory data grid is good for certain things. There are plenty of things that a traditional RDBMS is good at that any IMDG just doesn't support. An obvious issue is that memory is significantly more expensive than hard drives. 8GB of server grade memory costs thousands of dollars. 8GB of server grade disk space costs pennies. Even though the disk is slower than memory, we can store a lot more data on it. An IMDG shines where a sizeable portion of frequently-changing data can be cached so that all clients see the same data. The IMDG provides orders of magnitude with better latency, read, and write speeds than any RDBMS. But we need to be aware that, for large data sets, an entire data set may not fit in a typical IMDG. If we focus on the frequently-changing data that must be available to all clients, then using the IMDG makes sense. Imagine a deployment with 10 servers, each with 64GB of memory. Let's say that of the 64GB, we can use 50GB for ObjectGrid. For a 1TB data set, we can store 50% of it in cache. That's great! As the data set grows to 5TB, we can fit 10% in cache. That's not as good as 50%, but if it is the 10% of the data that is accessed most frequently, then we come out ahead. If that 10% of data has a lot of writes to it, then we come out ahead. Websphere eXtreme Scale gives us predictable, dynamic, and linear scalability. When our data set grows to 100TB, and the IMDG holds only 0.5% of the total data set, we can add more nodes to the IMDG and increase the total percentage of cacheable data (see below). It's important to note that this predictable scalability is immensely valuable. Predictable scalability makes capacity planning easier. It makes hardware procurement easier because you know what you need. Linear scalability provides a graceful way to grow a deployment as usage and data grow. You can rest easy knowing the limits of your application when it's using an IMDG. The IMDG also acts as a shock absorber in front of a database. We're going to explore some of the reasons why an IMDG makes a good shock absorber with the Loader functionality. There are plenty of other situations, some that we have already covered, where an IMDG is the correct tool for the job. There are also plenty of situations where an IMDG just doesn't fit. A traditional RDBMS has thousands of man-years of research, implementation tuning, and bug fixing already put into it. An RDBMS is well-understood and is easy to use in application development. There are standard APIs for interacting with them in almost any language: In-memory data grids don't have the supporting tools built around them that RDBMSs have. We can't plug CrystalReports into an ObjectGrid instance to get daily reports out of the data in the grid. Querying the grid is useful when we run simple queries, but fails when we need to run the query over the entire data set, or run a complex query. The query engine in Websphere eXtreme Scale is not as sophisticated as the query engine in an RDBMS. This also means the data we get from ad hoc queries is limited. Running ad hoc queries in the first place is more difficult. Even building an ad hoc query runner that interacts with an IMDG is of limited usefulness. An RDBMS is a wonderful cross-platform data store. Websphere eXtreme Scale is written in Java and only deals with Java objects. A simple way for an organization to share data between applications is in a plaintext database. We have standard APIs for database access in nearly every programming language. As long as we use the supported database driver and API, we will get the results as we expect, including ORM frameworks from other platforms like .NET and Rails. We could go on and on about why an RDBMS needs to be in place, but I think the point is clear. It's something we still need to make our software as useful as possible.
Read more
  • 0
  • 0
  • 2157

article-image-build-advanced-contact-manager-using-jboss-richfaces-33-part-2
Packt
18 Nov 2009
10 min read
Save for later

Build an Advanced Contact Manager using JBoss RichFaces 3.3: Part 2

Packt
18 Nov 2009
10 min read
The contact detail For the third column, we would like to show three different statuses: The "No contact selected" message when no contact is selected (so the property is null) A view-only box when we are not in the edit mode (the property selectedContactEditing is set to false) An edit box when we are in the edit mode (the property selectedContactEditing is set to true) So, let's open the home.xhtml page and insert the third column inside the panel grid with the three statuses: <a:outputPanel id="contactDetail"> <a:outputPanel rendered="#{homeSelectedContactHelper. selectedContact==null}"> <rich:panel> <h:outputText value="#{messages['noContactSelected']}"/> </rich:panel></a:outputPanel> <a:outputPanel rendered="#{homeSelectedContactHelper. selectedContact!=null and homeSelectedContactHelper. selectedContactEditing==false}"> <ui:include src="main/contactView.xhtml"/> </a:outputPanel> <a:outputPanel rendered="#{homeSelectedContactHelper. selectedContact!=null and homeSelectedContactHelper. selectedContactEditing==true}"> <ui:include src="main/contactEdit.xhtml"/> </a:outputPanel></a:outputPanel> Here, we have put the main a:outputPanel as the main placeholder, and inside it we put three more instances of a:outputPanel (one for every state) with the rendered attribute in order to decide which one to show. The first one just shows a message when homeSelectedContactHelper.selectedContact is set to null: The second instance of a:outputPanel will include the main/contactView.xhtml file only if homeSelectedContactHelper.selectedContact is not null, and we are not in editing mode (so homeSelectedContactHelper.selectedContactEditing is set to false); the third one will be shown only if homeSelectedContactHelper.selectedContact is not null, and we are in the edit mode (that is homeSelectedContactHelper.selectedContactEditing is equal to true). Before starting to write the include sections, let's see how the main bean for the selected contact would look, and connect it with the data table for selecting the contact from it. The support bean Let's create a new class called HomeSelectedContactHelper inside the book.richfaces.advcm.modules.main package; the class might look like this: @Name("homeSelectedContactHelper")@Scope(ScopeType.CONVERSATION)public class HomeSelectedContactHelper { @In(create = true) EntityManager entityManager; @In(required = true) Contact loggedUser; @In FacesMessages facesMessages; // My code here} This is a standard JBoss Seam component and  now let's add the properties. The bean that we are going to use for view and edit features is very simple to understand—it just contains two properties (namely selectedContact and selectedContactEditing) and some action methods to manage them. Let's add the properties to our class: private Contact selectedContact;private Boolean selectedContactEditing;public Contact getSelectedContact() { return selectedContact;}public void setSelectedContact(Contact selectedContact) { this.selectedContact = selectedContact;}public Boolean getSelectedContactEditing() { return selectedContactEditing;}public void setSelectedContactEditing(Boolean selectedContactEditing) { this.selectedContactEditing = selectedContactEditing;} As you can see, we just added two properties with standard the getter and setter. Let's now see the action methods: public void createNewEmptyContactInstance() { setSelectedContact(new Contact());}public void insertNewContact() { // Attaching the owner of the contact getSelectedContact().setContact(loggedUser); entityManager.persist(getSelectedContact()); facesMessages.addFromResourceBundle(StatusMessage.Severity.INFO,  "contactAdded");}public void saveContactData() { entityManager.merge(getSelectedContact()); facesMessages.addFromResourceBundle(StatusMessage.Severity.INFO, "contactSaved");}public void deleteSelectedContact() { entityManager.remove(getSelectedContact()); // De-selecting the current contact setSelectedContact(null); setSelectedContactEditing(null); facesMessages.addFromResourceBundle(StatusMessage.Severity.INFO, "contactDeleted");}public boolean isSelectedContactManaged() { return getSelectedContact() != null && entityManager.contains (getSelectedContact());} It's not difficult to understand what they do, however, in order to be clear, we are going to describe what each method does. The method createNewEmptyContactInstance() simply sets the selectedContact property with a new instance of the Contact class—it will be called by the "add contact" button. After the user has clicked on the "add contact" button and inserted the contact data, he/she has to persist this new instance of data into the database. It is done by the insertNewContact() method, called when he/she clicks on the Insert button. If the user edits a contact and clicks on the "Save" button, the saveContactData() method will be called, in order to store the modifications into the database. As for saving, the deleteSelectedContact() method will be called by the "Delete" button, in order to remove the instance from the database. A special mention for the isSelectedContactManaged() method—it is used to determine if the selectedContact property contains a bean that exists in the database (so, I'm editing it), or a new instance not yet persisted to the database. We use it especially in rendered properties, in order to determine which component to show (you will see this in the next section). Selecting the contact from the contacts list We will use the contacts list in order to decide which contact must be shown in the detail view. The simple way is to add a new column into the dataTable, and put a command button (or link) to select the bean in order to visualize the detail view. Let's open the contactsList.xhtml file and add another column as follows: <rich:column width="10%" style="text-align: center"> <a:commandButton image="/img/view.png" reRender="contactDetail"> <f:setPropertyActionListener value="#{contact}" target="#{homeSelectedContactHelper.selectedContact}"/> <f:setPropertyActionListener value="#{false}" target="#{homeSelectedContactHelper.selectedContactEditing}"/> </a:commandButton></rich:column> Inside the column, we added the a:commandButton component (that shows an image instead of the standard text) that doesn't call any action—it uses the f:setPropertyAction method to set the homeSelectedContactHelper.selectedContact value to contact (the row value of the dataTable), and to tell to show the view box and not the edit one (setting homeSelectedContactHelper.selectedContactEditing to false). After the Ajax call, it will re-render the contactDetail box in order to reflect the change. Also, the header must be changed to reflect the column add: <rich:dataTable ... > <f:facet name="header"> <rich:columnGroup> <rich:column colspan="3"> <h:outputText value="Contacts"/> </rich:column> <rich:column breakBefore="true"> <h:outputText value="Name"/> </rich:column> <rich:column> <h:outputText value="Surname"/> </rich:column> <rich:column> <rich:spacer/> </rich:column> </rich:columnGroup> </f:facet> ... We incremented the colspan attribute value and added a new (empty) column header. The new contacts list will look like the following screenshot: Adding a new contact Another feature we would like to add to the contacts list is the "Add contact" button. In order to do that, we are going to use the empty toolbar. Let's add a new action button into the rich:toolbar component: <a:commandButton image="/img/addcontact.png" reRender="contactDetail"action="#{homeSelectedContactHelper.createNewEmptyContactInstance}"> <f:setPropertyActionListener value="#{true}"target="#{homeSelectedContactHelper.selectedContactEditing}"/></a:commandButton> This button will call the homeSelectedContactHelper.createNewEmptyContactInstance() action method in order to create and select an empty instance and will set homeSelectedContactHelper.selectedContactEditing to true in order to start the editing; after those Ajax calls, it will re-render the contactDetail box to reflect the changes. Viewing contact detail We are ready to implement the view contact detail box; just open the /view/main/contactView.xhtml file and add the following code: <h:form> <rich:panel> <f:facet name="header"> <h:outputText value="#{homeSelectedContactHelper.selectedContact.name} #{homeSelectedContactHelper.selectedContact.surname}"/> </f:facet> <h:panelGrid columns="2" rowClasses="prop" columnClasses="name,value"> <h:outputText value="#{messages['name']}:"/> <h:outputText value="#{homeSelectedContactHelper.selectedContact.name}"/> <h:outputText value="#{messages['surname']}:"/> <h:outputText value="#{homeSelectedContactHelper.selectedContact.surname}"/> <h:outputText value="#{messages['company']}:"/> <h:outputText value="#{homeSelectedContactHelper.selectedContact.company}"/> <h:outputText value="#{messages['email']}:"/> <h:outputText value="#{homeSelectedContactHelper.selectedContact.email}"/> </h:panelGrid> </rich:panel> <rich:toolBar> <rich:toolBarGroup> <a:commandLink ajaxSingle="true" reRender="contactDetail" styleClass="image-command-link"> <f:setPropertyActionListener value="#{true}" target="#{homeSelectedContactHelper.selectedContactEditing}"/> <h:graphicImage value="/img/edit.png" /> <h:outputText value="#{messages['edit']}" /> </a:commandLink> </rich:toolBarGroup> </rich:toolBar></h:form> The first part is just rich:panel containing h:panelGrid with the fields' detail. In the second part of the code, we put rich:toolBar containing a command link (with an image and a text) that activates the edit mode—it, in fact, just sets the homeSelectedContactHelper.selectedContactEditing property to true and re-renders contactDetail in order to make it appear in the edit box. We also added a new CSS class into the /view/stylesheet/theme.css file to manage the layout of command links with images: .image-command-link { text-decoration: none;}.image-command-link img { vertical-align: middle; padding-right: 3px;} The view box looks like: We are now ready to develop the edit box. Editing contact detail When in the edit mode, the content of the /view/main/contactEdit.xhtml file will be shown in the contact detail box—let's open it for editing. Let's add the code for creating the main panel: <h:form> <rich:panel> <f:facet name="header"> <h:panelGroup> <h:outputText value="#{homeSelectedContactHelper.selectedContact.name} #{homeSelectedContactHelper.selectedContact.surname}"rendered="#{homeSelectedContactHelper.selectedContactManaged}"/> <h:outputText value="#{messages['newContact']}"rendered="#{!homeSelectedContactHelper.selectedContactManaged}"/> </h:panelGroup> </f:facet> <!-- my code here --> </rich:panel><!-- my code here --></h:form> This is a standard rich:panel with a customized header—it has two h:outputText components that will be shown depending on the rendered attribute (whether it's a new contact or not). More than one component inside f:facetRemember that f:facet must have only one child, so, to put more than one component, you have to use a surrounding one like h:panelGroup or something similar. Inside the panel, we are going to put h:panelGrid containing the components for data editing: <rich:graphValidator> <h:panelGrid columns="3" rowClasses="prop" columnClasses="name,value,validatormsg"> <h:outputLabel for="scName" value="#{messages['name']}:"/> <h:inputText id="scName" value="#{homeSelectedContactHelper.selectedContact.name}"/> <rich:message for="scName" styleClass="messagesingle" errorClass="errormsg" infoClass="infomsg" warnClass="warnmsg"/> <h:outputLabel for="scSurname" value="#{messages['surname']}:"/> <h:inputText id="scSurname" value="#{homeSelectedContactHelper.selectedContact.surname}"/> <rich:message for="scSurname" styleClass="messagesingle" errorClass="errormsg" infoClass="infomsg" warnClass="warnmsg"/> <h:outputLabel for="scCompany" value="#{messages['company']}:"/> <h:inputText id="scCompany" value="#{homeSelectedContactHelper.selectedContact.company}"/> <rich:message for="scCompany" styleClass="messagesingle" errorClass="errormsg" infoClass="infomsg" warnClass="warnmsg"/> <h:outputLabel for="scEmail" value="#{messages['email']}:"/> <h:inputText id="scEmail" value="#{homeSelectedContactHelper.selectedContact.email}"/> <rich:message for="scEmail" styleClass="messagesingle" errorClass="errormsg" infoClass="infomsg" warnClass="warnmsg"/> </h:panelGrid><rich:graphValidator> Nothing complicated here, we've just used h:outputLabel, h:inputText, and rich:message for every Contact property to be edited; it appears as follows:
Read more
  • 0
  • 0
  • 1330

article-image-user-interaction-and-email-automation-symfony-13-part1
Packt
18 Nov 2009
14 min read
Save for later

User Interaction and Email Automation in Symfony 1.3: Part1

Packt
18 Nov 2009
14 min read
The signup module We want to provide the users with the functionality to enter their name, email address, and how they found our web site. We want all this stored in a database and to have an email automatically sent out to the users thanking them for signing up. To start things off, we must first add some new tables to our existing database schema. The structure of our newsletter table will be straightforward. We will need one table to capture the users' information and a related table that will hold the names of all the places where we advertised our site. I have constructed the following entity relationship diagram to show you a visual relationship of the tables: All the code used in this article can be accessed here. Let's translate this diagram into XML and place it in the config/schema.xml file: <table name="newsletter_adverts" idMethod="native" phpName="NewsletterAds"> <column name="newsletter_adverts_id" type="INTEGER" required="true" autoIncrement="true" primaryKey="true" /> <column name="advertised" type="VARCHAR" size="30" required="true" /> </table> <table name="newsletter_signups" idMethod="native" phpName="NewsletterSignup"> <column name="id" type="INTEGER" required="true" autoIncrement="true" primaryKey="true" /> <column name="first_name" type="VARCHAR" size="20" required="true" /> <column name="surname" type="VARCHAR" size="20" required="true" /> <column name="email" type="VARCHAR" size="100" required="true" /> <column name="activation_key" type="VARCHAR" size="100" required="true" /> <column name="activated" type="BOOLEAN" default="0" required="true" /> <column name="newsletter_adverts_id" type="INTEGER" required="true"/> <foreign-key foreignTable="newsletter_adverts" onDelete="CASCADE"> <reference local="newsletter_adverts_id" foreign="newsletter_adverts_id" /> </foreign-key> <column name="created_at" type="TIMESTAMP" required="true" /> <column name="updated_at" type="TIMESTAMP" required="true" /> </table> We will need to populate the newsletter_adverts table with some test data as well. Therefore, I have also appended the following data to the fixtures.yml file located in the data/fixtures/ directory: NewsletterAds: nsa1: advertised: Internet Search nsa2: advertised: High Street nsa3: advertised: Poster With the database schema and the test data ready to be inserted into the database, we can once again use the Symfony tasks. As we have added two new tables to the schema, we will have to rebuild everything to generate the models using the following command: $/home/timmy/workspace/milkshake>symfony propel:build-all-load --no-confirmation Now we have populated the tables in the database, and the models and forms have been generated for use too. Binding a form to a database table Symfony contains a whole framework just for the development of forms. The forms framework makes building forms easier by applying object-oriented methods to their development. Each form class is based on its related table in the database. This includes the fields, the validators, and the way in which the forms and fields are rendered. A look at the generated base class Rather than starting off with a simple form, we are going to look at the base form class that has already been generated for us as a part of the build task we executed earlier. Because the code is generated, it will be easier for you to see the initial flow of a form. So let's open the base class for the NewsletterSignupForm form. The file is located at lib/form/base/BaseNewsletterSignupForm.class.php: class BaseNewsletterSignupForm extends BaseFormPropel { public function setup() { $this->setWidgets(array( 'id' => new sfWidgetFormInputHidden(), 'first_name' => new sfWidgetFormInput(), 'surname' => new sfWidgetFormInput(), 'email' => new sfWidgetFormInput(), 'activation_key' => new sfWidgetFormInput(), 'activated' => new sfWidgetFormInputCheckbox(), 'newsletter_adverts_id' => new sfWidgetFormPropelChoice (array('model' => 'NewsletterAds', 'add_empty' => false)), 'created_at' => new sfWidgetFormDateTime(), 'updated_at' => new sfWidgetFormDateTime(), )); $this->setValidators(array( 'id' => new sfValidatorPropelChoice(array ('model' => 'NewsletterSignup', 'column' => 'id', 'required' => false)), 'first_name' => new sfValidatorString(array('max_length' => 20)), 'surname' => new sfValidatorString(array('max_length' => 20)), 'email' => new sfValidatorString(array('max_length' => 100)), 'activation_key' => new sfValidatorString(array('max_length' => 100)), 'activated' => new sfValidatorBoolean(), 'newsletter_adverts_id'=> new sfValidatorPropelChoice(array ('model' => 'NewsletterAds', 'column' => 'newsletter_adverts_id')), 'created_at' => new sfValidatorDateTime(), 'updated_at' => new sfValidatorDateTime(), )); $this->widgetSchema->setNameFormat('newsletter_signup[%s]'); $this->errorSchema = new sfValidatorErrorSchema ($this->validatorSchema); parent::setup(); } There are five areas in this base class that are worth noting: This base class extends the BaseFormPropel class, which is an empty class. All base classes extend this class, which allows us to add global settings to all our forms. All of the columns in our table are treated as fields in the form, and are referred to as widgets. All of these widgets are then attached to the form by adding them to the setWidgets() method. Looking over the widgets in the array, you will see that they are pretty standard, such as sfWidgetFormInputHidden(), sfWidgetFormInput(). However, there is one widget added that follows the relationship between the newsletter_sigups table and the newsletter_adverts table. It is the sfWidgetFormPropelChoice widget. Because there is a 1:M relation between the tables, the default behavior is to use this widget, which creates an HTML drop-down box and is populated with the values from the newsletter_adverts table. As a part of the attribute set, you will see that it has set the model needed to retrieve the values to NewsletterAds and the newsletter_adverts_id column for the actual values of the drop-down box. All the widgets on the form must be validated by default. To do this, we have to call the setValidators() method and add the validation requirements to each widget. At the moment, the generated validators reflect the attributes of our database as set in the schema. For example, the first_name field in the statement 'first_name' => new sfValidatorString(array('max_length' => 20)) demonstrates that the validator checks if the maximum length is 20. If you remember, in our schema too, the first_name column is set to 20 characters. The final part calls the parent's setup() function. The base class BaseNewsletterSignupForm contains all the components needed to generate the form for us. So let's get the form on a page and take a look at the method to customize it. There are many widgets that Symfony provides for us. You can find the classes for them inside the widget/ directory of your Symfony installation. The Symfony propel task always generates a form class and its corresponding base class. Of course, not all of our tables will need to have a form bound to them. Therefore, delete all the form classes that are not needed. Rendering the form Rendering this basic form requires us to instantiate the form object in the action. Assigning the form object to the global $this variable means that we can pass the form object to the template just like any other variable. So let's start by implementing the newsletter signup module. In your terminal window, execute the generate:module task like this: $/home/timmy/workspace/milkshake>symfony generate:module frontend signup Now we can start with the application logic. Open the action class from apps/frontend/modules/signup/actions/actions.class.php for the signup module and add the following logic inside the index action: public function executeIndex(sfWebRequest $request) { $this->form = new NewsletterSignupForm(); return sfView::SUCCESS; } As I had mentioned earlier, the form class deals with the form validation and rendering. For the time being, we are going to stick to the default layout by allowing the form object to render itself. Using this method initially will allow us to create rapid prototypes. Let's open the apps/frontend/signup/templates/indexSuccess.php template and add the following view logic: <form action="<?php echo url_for('signup/submit') ?>" method="POST"> <table><?php echo $form ?></table> <input type="submit" /> </form> The form class is responsible for rendering of the form elements only. Therefore, we have to include the <form> and submit HTML tags that wrap around the form. Also, the default format of the form is set to 'table'. Again, we must also add the start and end tags of the <table>. At this stage, we would normally be able to view the form in the browser. But doing so will raise a Symfony exception error. The cause of this is that the results retrieved from the newsletter_adverts table are in the form of an array of objects. These results need to populate the select box widget. But in the current format, this is not possible. Therefore, we have to convert each object into its string equivalent. To do this, we need to create a PHP magic function of __toString() in the DAO class NewsletterAds. The DAO class for NewlsetterAds is located at lib/model/NewsletterAds.php just as all of the other models. Here we need to represent each object as its name, which is the value in the advertised column. Remember that we need to add this method to the DAO class as this represents a row within the results, unlike the peer class that represents the entire result set. Let's add the function to the NewsletterAds class as I have done here: class NewsletterAds extends BaseNewsletterAds { public function __toString() { return $this->getAdvertised(); } } We are now ready to view the completed form. In your web browser, enter the URL http://milkshake/frontend_dev.php/signup and you will see the result shown in the following screenshot: As you can see, although the form has been rendered according to our table structure, the fields which we do not want the user to fill in are also included. Of course, we can change this quiet easily. But before we take a look at the layout of the form, let's customize the widgets and widget validators. Now we can begin working on the application logic for submitting the form. Customizing form widgets and validators All of the generated form classes are located in the lib/form and the lib/form/base directories. The latter is where the default generated classes are located, and the former is where the customizable classes are located. This follows the same structure as the models. Each custom form class inherits from its parent. Therefore, we have to override some of the functions to customize the form. Let's customize the widgets and validators for the NewsletterSignupForm. Open the lib/forms/NewsletterSignupForm.class.php file and paste the following code inside the configure() method: //Removed unneeded widgets unset( $this['created_at'], $this['updated_at'], $this['activation_key'], $this['activated'], $this['id'] ); //Set widgets //Modify widgets $this->widgetSchema['first_name'] = new sfWidgetFormInput(); $this->widgetSchema['newsletter_adverts_id'] = new sfWidgetFormPropelChoice(array('model' => 'NewsletterAds', 'add_empty' => true, 'label'=>'Where did you find us?')); $this->widgetSchema['email'] = new sfWidgetFormInput (array('label' => 'Email Address')); //Add validation $this->setValidators(array ('first_name'=> new sfValidatorString(array ('required' => true), array('required' => 'Enter your firstname')), 'surname'=> new sfValidatorString(array('required' => true), array('required' => 'Enter your surname')), 'email'=> new sfValidatorString(array('required' => true), array('invalid' => 'Provide a valid email', 'required' => 'Enter your email')), 'newsletter_adverts_id' => new sfValidatorPropelChoice(array('model' => 'NewsletterAds', 'column' => 'newsletter_adverts_id'), array('required' => 'Select where you found us')), )); //Set post validators $this->validatorSchema->setPostValidator( new sfValidatorPropelUnique(array('model' => 'NewsletterSignup', 'column' => array('email')), array('invalid' => 'Email address is already registered')) ); //Set form name $this->widgetSchema->setNameFormat('newsletter_signup[%s]'); //Set the form format $this->widgetSchema->setFormFormatterName('list'); Let's take a closer look at the code. Removing unneeded fields To remove the fields that we do not want to be rendered, we must call the PHP unset() method and pass in the fields to unset. As mentioned earlier, all of the fields that are rendered need a corresponding validator, unless we unset them. Here we do not want the created_at and activation_key fields to be entered by the user. To do so, the unset() method should contain the following code: unset( $this['created_at'], $this['updated_at'], $this['activation_key'], $this['activated'], $this['id'] ); Modifying the form widgets Although it'll be fine to use the remaining widgets as they are, let's have a look at how we can modify them: //Modify widgets $this->widgetSchema['first_name'] = new sfWidgetFormInput(); $this->widgetSchema['newsletter_adverts_id'] = new sfWidgetFormPropelChoice(array('model' => 'AlSignupNewsletterAds', 'add_empty' => true, 'label'=>'Where did you find us?')); $this->widgetSchema['email'] = new sfWidgetFormInput(array('label' => 'Email Address')); There are several types of widgets available, but our form requires only two of them. Here we have used the sfWidgetFormInput() and sfWidgetFormPropelChoice() widgets. Each of these can be initialized with several values. We have initialized the email and newsletter_adverts_id widgets with a label. This basically renders the label field associated to the widget on the form. We do not have to include a label because Symfony adds the label according to the column name. Adding form validators Let's add the validators in a similar way as we have added the widgets: //Add validation $this->setValidators(array( 'first_name'=> new sfValidatorString(array('required' => true), array('required' => 'Enter your firstname')), 'surname'=> new sfValidatorString(array('required' => true), array('required' => 'Enter your surname')), 'email'=> new sfValidatorEmail(array('required' => true), array('invalid' => 'Provide a valid email', 'required' => 'Enter your email')), 'newsletter_adverts_id' => new sfValidatorPropelChoice(array ('model' => 'NewsletterAds', 'column' => 'newsletter_adverts_id'), array('required' => 'Select where you found us')), )); //Set post validators $this->validatorSchema->setPostValidator(new sfValidatorPropelUnique(array('model' => 'NewsletterSignup', 'column' => array('email')), array('invalid' => 'Email address is already registered')) ); Our form will need four different types of validators: sfValidatorString: This checks the validity of a string against a criteria. It takes four arguments—required, trim, min_length, and max_length. SfValidatorEmail: This validates the input against the pattern of an email address. SfValidatorPropelChoice: It validates the value with the values in the newsletter_adverts table. It needs the model and column that are to be used.   SfValidatorPropelUnique: Again, this validator checks the value against the values in a given table column for uniqueness. In our case, we want to use the NewsletterSignup model to test if the email column is unique. As mentioned earlier, all the fields must have a validator. Although it's not recommended, you can allow extra parameters to be passed in. To achieve this, there are two steps: You must disable the default option of having all fields validated by $this->validatorSchema->setOption('allow_extra_fields', true). Although the above step allows the values to bypass validation, they will be filtered out of the results. To prevent this, you will have to set $this->validatorSchema->setOption('filter_extra_fields', false). Form naming convention and setting its style The final part we added is the naming convention for the HTML attributes and the style in which we want the form rendered. The HTML output will use our naming convention. For example, in the following code, we have set the convention to newsletter_signup[fieldname] for each input field's name. //Set form name $this->widgetSchema->setNameFormat('newsletter_signup[%s]'); //Set the form format $this->widgetSchema->setFormFormatterName('list'); Two formats are shipped with Symfony that we can use to render our form. We can either render it in an HTML table or an unordered list. As we have seen, the default is an HTML table. But by setting this as list, the form is now rendered as an unordered HTML list, just like the following screenshot. (Of course, I had to replace the <table> tags with the <ul> tags.)
Read more
  • 0
  • 0
  • 2207

article-image-archiva-team-part-2
Packt
18 Nov 2009
7 min read
Save for later

Archiva in a Team: Part 2

Packt
18 Nov 2009
7 min read
Deleting artifacts in your repository Sometimes the need for deleting artifacts from the repository arises. For example, if an artifact was deployed by accident to the repository or the artifact has already been released but an old snapshot version is still available. In Archiva, there are different ways of deleting artifacts from the repository—through WebDAV, via the web application, through the scheduled repository purging, or by directly deleting it in the file system. It is not recommended that artifacts be deleted directly from the file system. Not only does it require access to the server itself, it is also prone to error. Artifacts that should not be deleted could be deleted by mistake. In case you still want to directly delete an artifact from the file system, all files related to the artifact such as metadata files and checksums must also be deleted. The repository must be scanned as well in order to update the metadata files. This can be done by clicking the Scan Repository Now button of the repository configuration in the Repositories page. The database scanning also needs to be explicitly executed to immediately remove the deleted artifact from the database. One of the advantages of using the Delete Artifact form in the web application is that you do not need to have direct access to the server. All you need is the required Archiva permissions, which come with the Repository Manager role (without the permissions Delete Artifact will not be visible in the navigation menu). Another advantage is that the repository scanning no longer needs to be explicitly executed as Archiva already executes the repository and database scanning consumers to update the index and the database for you. Now, let's try deleting an old artifact from one of the repositories. If you go to http://localhost:8081/archiva/repository/snapshots/com/effectivemaven/centrepoint/centrepoint, the old 1.0-SNAPSHOT version of the project still exists. We will remove this artifact from the repository using the delete artifact web form. First, click Delete Artifact from the navigation menu and then fill in the form as follows: Click the Submit button. After the artifact has been deleted, you should see the confirmation message Artifact 'com.effectivemaven.centrepoint:centrepoint:1.0-SNAPSHOT' was successfully deleted from repository 'snapshots'. If you browse the repository at http://localhost:8081/archiva/repository/snapshots, the related artifacts such as the POM, maven-metadata.xml, and the checksums were also deleted. To delete artifacts through WebDAV, just open the repository using a WebDAV client and delete the artifact like in a regular file system. As for the scheduled repository purging, we will discuss this in the following sections. We have tackled the subjects of repository groups, RSS feeds, and deleting artifacts in the repository. This article would never be complete without covering repository maintenance. The succeeding sections will be all about that. The Archiva reports Archiva generates two types of reports. These are the repository statistics, providing information such as statistical data of a repository's content and the repository health report, which makes us aware of any problems in the repository such as artifacts that have invalid POM files. Both accept different criteria for customizing the generated output as seen in the following screenshot: Now, let's discuss the configuration for each report. Repository statistics This report provides statistical repository information such as the total number of artifacts in the repository, its total size, the number of plugins in the repository, and the likes based on a given repository scan execution time. This report can be used for analyzing the current content of your repositories, and tracking its growth, usage, and evolution over time. The report can be constrained by the given Start Date and End Date. If no Start Date and End Date are provided, all statistics right from the start up to the current date will be included in the report (to a maximum of the number of rows given in the Row Count). For the Repository Statistics, we can also configure the Repositories To Be Compared. If only one repository is selected in Repositories To Be Compared, the generated report will contain details of a single repository. The following is a sample report where only one repository is selected: Let's run through the contents of the sample Repository Statistics report given previously for repository internal. The Total File Count pertains to the total number of files in the repository during each execution of the repository scan. The Total Size, on the other hand, is the size (in bytes) of the repository at that time. The number of unique groups and artifact names are broken down in the report as well as the number of plugins, archetypes, JAR, and WAR files. The last two columns—number of deployments and artifact requests—are not yet implemented but will be fixed in the future releases. On the other hand, if more than one repository is selected in the Repositories To Be Compared, the generated report would contain a comparison of the latest statistics of the repositories based on the specified End Date. This is useful for tracking which repositories are the most utilized. For example, if different development groups host their own repositories, the comparison can show which groups are using the most space. Look at the following screenshot for a sample comparison report to see the difference from the previous one: To allow you to view this report outside of the web application, the report can be exported as a CSV file by clicking on the Export to CSV link. You should be able to open the exported file as an Excel spreadsheet. Repository health One of the secrets behind a successful and reproducible build is a clean and healthy repository. Corrupt metadata or an invalid or missing POM file are the usual causes for a build to break. To prevent this from happening, we must ensure that the repositories we are getting our artifacts from are in good health. Archiva provides a way of doing this through the Repository Health report and its built-in utilities for updating metadata and fixing checksums. The Repository Health report provides a detailed list of artifacts in the repository that are found to be defective. It gives a starting point for correcting any problems and can be used when diagnosing build errors with a particular artifact. For example, a common reason for an artifact being defective is when the version of the artifact specified in the POM is different from the actual version in its filename. This could easily happen when using deploy:deploy-file (or even using the Archiva web upload form) as the actual filename used for the uploaded artifact is determined based on the supplied parameters. It is a possibility that the included POM in the upload has different coordinates from the provided parameters. These defects are discovered during Archiva's database scan, when the actual POM file is read and added to the database. We can narrow down the report by providing a specific Group ID and/or a Repository ID which will be used for querying defective artifacts that match these criteria. If you try querying for the report using the default configuration, you should be able to see a generated report similar to the following one, which shows a defective POM in repository internal. To repair such an error, you can manually fix the POM in the Archiva repository by updating it in the file system. If the defect is caused by a transfer error when the artifact was proxied, you can delete the artifact (including the metadata and checksums) then force Archiva to retrieve it again by requesting it. A word of caution though—making these changes could affect the reproducibility of a dependent project's build. For example, it is possible that the actual artifact in the central repository is the defective one. If you fixed the artifact in your internal Archiva repository, project builds that go through the local proxy may get a successful build. However, the project is built directly off central and the build fails because the dependency artifact is defective. That summarizes monitoring the health of our repositories. The next section discusses the built-in Archiva utilities which in one way or another clean up and repair broken artifacts and metadata in the repositories.    
Read more
  • 0
  • 0
  • 1572

article-image-quality-assurance-asterisk-16
Packt
18 Nov 2009
10 min read
Save for later

Quality Assurance in Asterisk 1.6

Packt
18 Nov 2009
10 min read
The world has changed quite a bit in the last 150 years. Over this time, the telephone system has been invented, improved, and automated. Telephone switches no longer refer to people sitting in a large room connecting wires between the appropriate jacks. Flexible and powerful telephone service has moved from a dream to an expectation in large businesses, and for most of us it is a necessity. Today, telephone systems are the lifeblood of business. They are how we take orders, acquire supplies, and even call for emergency assistance. With the increase in prominence of telephones, the expectations of telephone users have increased proportionally. Not only have the technological expectations for telephone systems increased dramatically, but consumers are expecting more and more out of the businesses they call. Customers expect to be helped quickly and professionally. They want to know everything in a matter of minutes. Roads do not hold the only rage our society is facing today. As a business we have a variety of questions relating to our telephone system such as: How are our personnel handling angry callers? Are our employees answering the calls in a reasonable amount of time? Do we have any workers using the phone system for personal calls when they should be doing their job? We will never be able to make sure everybody does what they are supposed to do all of the time. What we will be able to do at the end of this article is perform spot-checks on how we are doing on customer service, and make sure our phone service isn't being used for unauthorized purposes. Ultimately, it comes down to a matter of trust; however, some people do not know better because they haven't been fully trained. Most will always act honorably; however, some just cannot and should not be trusted. We will try to find out who is who. Call Detail Records When we talk about security, what images come to mind? May be a big, burly guard? Perhaps a bunch of guys in green, carrying machine guns? Do we imagine a person with a metal-detecting wand? Or do we think of thick glass window panes? All of these are security features. It is just that some are a little more intrusive than others. Each time we increase security, we become a little bit less friendly. We all have to decide how far we are willing (and able) to go. In the continuum of security, Call Detail Records are the least intrusive. No special usernames or passwords have to be remembered. No fear of big brother breathing down your customers' and users' necks need be felt. We are simply doing the same thing telephone companies do—tracking what calls were made, when they were made, how long they lasted, where they came from, and a few other bits of information. This information is then available for us to review at our leisure. Asterisk gives us a few options on how we track this information. The two major choices are flat-file logging and database logging. Flat-file CDR logging By default, Asterisk includes a module called cdr_csv. Right out of the box, Asterisk logs all calls coming in and going out. The information for these calls is placed in a Comma Separated Value (CSV) file. This CSV file is located in var/log/asterisk/cdr-csv. All information is available in Master.csv, and some channels can be configured to send some information to other files as well. The benefit of using a CSV file is the simplicity. Right after compiling and installing Asterisk, this method will work. No additional configuration is required. Also, no additional network traffic is generated, and no additional services have to be installed on our server. When using the CSV form of CDR, we will see lists and lists of values. They are not very easy to parse, so here is the format, in the order in which they appear: account code: As determined by the channel (for DAHDI) or the user (for IAX and SIP) source: The source of the call destination: The destination of the call destination context caller ID channel: The channel of the source destination channel: If applicable last application: The last application run on the channel last application argument: The last argument to the last application on the channel start time: The time the call commenced answer time: The time the call was answered end time: The time the call ended duration: The difference between start time and end time billable seconds: The difference between answer time and end time, which must be less than the duration disposition: Either ANSWERED, NO ANSWER, or BUSY amaflags: As set for the channel or user, like account code uniqueid: A unique call identifier userfield: A user field set by the SetCDRUserField command We see that there are many items of information logged for each and every call. We can compare the billable seconds with our phone bill at the end of the month to make sure they're close. We can look at the destination and figure out if the calls were authorized. This gives us enough information to answer most questions we may have about a phone call. While we have enough information to answer questions, finding that answer is not very easy. We would have to scan through the entire file to try to find anything. If we are going to use an accounting package or reporting software, CSV may be exactly what we need. However, if we wish to use it in a more ad hoc sort of way, it is not very readable. Database CDR logging If we wish to read our CDR logs, it is most easily accomplished when the records are sortable. The easiest way to do this is to store our CDR records in a database. Even in this, Asterisk gives us choices. Included with Asterisk is support for PostgreSQL databases. In order to be able to install this, we must first have the postgresql-devel package installed on our system. If you have to install this package, you'll need to reinstall Asterisk. The automake system will automatically detect that we have the capability to use PostgreSQL and compile that module for us. Aside from the development packages we have installed, we will also need a PostgreSQL server somewhere in our network. It can be the same machine as the Asterisk server, but it doesn't necessarily need to be. In fact, it probably makes sense to have only one such database server on our network, and we don't want to tie up too much of our PBX's resources with database maintenance and storage. There is a script in /usr/src/asterisk/contrib/scripts/ called postgres_cdr.sql, which creates the correct table structure for us. This script should be run from the database server. If we get an error message while rebuilding that says something like "cannot find-lz", then we need to install zlib-devel. Now that we have set up our database and installed the CDR module, we must configure Asterisk to use the correct database. In order to do this, we need to edit /etc/asterisk/cdr_pgsql.conf. All of the configuration variables are in the global section. Our file should look like the following: [global]hostname=dbserver.mydomain.tldport=5432dbname=asteriskpassword=supersecretuser=asteriskuser Once we have these variables set, the next time we restart Asterisk, all CDR records will be logged in the database. If PostgreSQL is not our database of choice, we can use MySQL. This is not a part of the normal distribution of Asterisk. But as we have already installed asterisk-addons, we should already have the ability to use MySQL for CDR logging. Before we compile, we need to make sure that we have mysql-devel installed. First, we need to decide which version we're going to use. Because of some license quibbles, MySQL version 4.0 and later is not in the automatic package distribution chain. Instead, if we do need to download it, we will have to get it directly from www.mysql.com. However, the older version (3.x) will work with Asterisk and hence, you may wish to take a look at the differences between what version 3 offered and what later versions give us. Other than the development package mentioned, we will also need a MySQL server somewhere in our network. Just as with PostgreSQL, we can choose to have it on the same server as Asterisk, but for the same reasons, we probably shouldn't. Next, on the database server, we need to create the database with a user and a table for the CDR data. We do this by running the following code: # mysqladmin create database asteriskcdrdb # mysqlmysql> GRANT ALL PRIVILEGES   -> ON asteriskcdrdb.*   -> TO asteriskcdruser   -> IDENTIFIED BY 'changethis2yourpassword';mysql> USE asteriskcdrdb;mysql> CREATE TABLE cdr (   -> uniqueid varchar(32) NOT NULL default '',   -> userfield varchar(255) NOT NULL default '',   -> accountcode varchar(20) NOT NULL default '',   -> src varchar(80) NOT NULL default '',   -> dst varchar(80) NOT NULL default '',   -> dcontext varchar(80) NOT NULL default '',   -> clid varchar(80) NOT NULL default '',   -> channel varchar(80) NOT NULL default '',   -> dstchannel varchar(80) NOT NULL default '',   -> lastapp varchar(80) NOT NULL default '',   -> lastdata varchar(80) NOT NULL default '',   -> calldate datetime NOT NULL default '0000-00-00 00:00:00',   -> duration int(11) NOT NULL default '0',   -> billsec int(11) NOT NULL default '0',   -> disposition varchar(45) NOT NULL default '',   -> amaflags int(11) NOT NULL default '0'-> ); That's all there is to it! We only have to do this once, so it's really not so bad. Next, we have to modify the /etc/asterisk/cdr_mysql.conf file to correctly reflect our choices. [global]hostname=ourdbserver.ourdomain.tlddbname=asteriskcdrdbpassword=changethis2yourpassworduser=asteriskcdruserport=3306userfield=1 The next time we restart Asterisk, our CDR information will be stored in the MySQL database. What does that give us? We now have the ability to use a number of very powerful tools to search our CDR records to find trends and patterns.
Read more
  • 0
  • 0
  • 1680
Unlock access to the largest independent learning library in Tech for FREE!
Get unlimited access to 7500+ expert-authored eBooks and video courses covering every tech area you can think of.
Renews at €18.99/month. Cancel anytime
article-image-setting-your-moodle-grade-book
Packt
17 Nov 2009
3 min read
Save for later

Setting up your Moodle Grade Book

Packt
17 Nov 2009
3 min read
Set your classes up with no effort on your part Why would you need to "set your classes up"? Surely if the course is enroll able, your students can just enter and take part in all the activities? They can indeed, but unless you put a password (enrolment key) on the course then any students on your Moodle could enroll and take a look around. That might be fine if you have an "open door" policy to your course – but in the real world, I wouldn't expect extra students to come in and join my Advanced Level French class, and so the same might apply in Moodle.  Likewise, although my colleague and I share teaching resources and experiences, we don't actually mark each other's students' work – we have separate grade books. The same should apply to Moodle. If more than one class is sharing a Moodle course, it’s important that the classes are on different pages of the grade book rather than everyone all listed together. So how do we do this without having manually to add every single student?  If your Moodle admin hasn't done this for you already, then log in to your course and click on Groups in the course administration block: Now click on the button Create group as in the next screenshot: Write the name of your class in the Group Name box, as it might say in their timetable for example. If you wish to put a description of this class you may do so, but it isn’t necessary. Scroll down to the Enrolment key box and enter a password for this class only. (Hint: it might be easier for them to recall if you make it the name of their class) If you click Unmask you will be able to see what you are typing: Click the Save changes button, and you will be returned to the Groups page where you will see your class with (0) next to its name. That's telling you there are no students in there yet! Repeat the process with all the other classes sharing your Moodle course. Your Groups page might end up like this: And finally… click to go back to your main course page and then, in the course administration block, click on Settings. In the page that comes up next, set Group mode to Visible or Separate and in  Availability  set another password (enrollment key). This enrollment key never gets used! It is simply there to keep unwanted students out! It doesn’t matter what you set it to or even whether you yourself remember it or not.
Read more
  • 0
  • 0
  • 1701

article-image-providing-context-using-custom-text-upk-35
Packt
17 Nov 2009
10 min read
Save for later

Providing context using Custom Text in UPK 3.5

Packt
17 Nov 2009
10 min read
The Start Frame You may recall that the very first Frame in a Topic is the Start Frame. This is sometimes referred to as the Introduction frame, which is largely a throwback to OnDemand, as we shall see shortly. As this Frame is the first thing that a trainee will see when they carry out a Topic, coupled with the fact that this Frame is a "non-action" Frame, in that the user does not need to actively do anything (other than pressing Enter to continue), it is a good place to provide some additional information to the trainee. The first thing that you should explain in the Introduction frame is exactly what the trainee will be learning in the exercise. Certainly the title of the Topic should give them a clue, but this is not really detailed enough. A good source of information for this is the learning objectives of the course for which this exercise has been built, or the competencies, depending on your curriculum development process. For our sample exercise, we could use the bubble shown in the following screenshot: This is a good start, but we can do more. It is always useful, with training exercises, to use realistic business scenarios to explain what the trainee is doing, to put the keystrokes and mouse-clicks into a business context. Trainees are much more likely to remember information to which they can relate. Consider telling a story and walking the trainees through that story as they carry out the exercise. Although it is a fairly spurious example, we will continue with our sample exercise. Here, we could use the bubble shown in the following screenshot: So now the trainee has a good idea of what they will learn, and they have an example that they can relate to. The text is also directed at the trainee, so the trainee will feel actively involved. There is another strong argument for including a scenario in the form shown above. Consider the case where you are providing training for users in multiple locations (possibly countries) or departments, each of which has its own set of customers, products, and vendors. Users will always want to see exercises using their data: orders for their products, placed at their location, and so on. To keep everyone happy, you would need to develop a separate, customized Topic for each location or user group. If the basic process (and, most importantly for us, the Actions in the recording) is the same in each case, this is clearly inefficient. However, if you create a scenario, and say something like "You are a Customer Service Representative in the Tampa Service Center. Customer SunCo has phoned through an order for 1,000 gallons of regular gasoline. You need to record this order in the system so that it can be fulfilled by Fuel Services." then trainees who are not at the Tampa Service Center will at least understand that this is role play. It is make believe, and they shouldn't be concerned that they don't see products that they don't supply at their own location. So set the scene with a scenario in the Introduction pane, and then build on this throughout the exercise. Introduction Text: Version differences At this point, it is worth highlighting some key differences between the way the Introduction pane was handled prior to UPK 3.5, and the way that the Introduction pane is used in UPK 3.5. If you open an Outline Element in the Outline Editor, and select a Topic in the navigation tree, you will see that the lower-right portion of the screen is labeled Introduction, as shown in the following screenshot: The Outline Editor is used to organize content objects into the structure that the trainee will see. However, if we look at the published version of the Outline above, we will see the following: Where has the Introduction Frame gone? Put simply. UPK 3.5 does not display the Introduction Frame in the Player any more. Users of UPK 2.x or OnDemand 9.x will recall that the Introduction Frame certainly used to be displayed, as shown in the following screenshot, which is taken from OnDemand 9.1.5: Quite why Oracle decided to change the way the Introduction pane is displayed (or not) in version 3.5 is a mystery (maybe they felt that with all that screen space required for the new Oracle branding, there just wasn't the space left to include the Introduction any more). However, it does have some important implications on the way that we are planning on using it. This is because the effect of a publishing option associated with the Introduction pane has changed significantly. In the Publishing Wizard, the options for the Player package include an option to Show introduction text. In OnDemand version 9.1, this option determined whether the Introduction text appeared in the Outline as well as on the first Frame of the Player. However, in UPK 3.5, the Introduction text is never displayed in the Outline and the Show introduction text determines whether the Introduction text appears in the Player at all (effectively, it controls whether the Start Frame is included in the Player or not. Version Difference The Content Development manual for OnDemand 9.1.5 describes the Show introduction text option as working the way described for UPK 3.5. It doesn't work that way; it works the way described for OnDemand 9.1, above. This is clearly a rare case of the documentation being updated before the software. For our purposes, therefore, we need to make sure that the Show introduction text option is always selected when we publish. This option can be found in the Player category of your Options, as shown in the next screenshot: Action Frames It is possible to add Custom Text to the Topic's Bubbles, either in addition to, or instead of, the Template Text. Using the Template Text has several significant advantages, especially when localizing your content or providing sound. However, the Template Text will only ever be able to describe the mechanics of what the user is doing, it cannot provide business context. You should always try to teach more than just key-strokes and mouse-clicks. Specifically, you should always take the opportunity to add business context yourself, through the liberal use of Custom Text in Action Frames. Consider the following example that uses solely the default template texts: Certainly the trainee can carry out the required action and work their way through the exercise, but are they really learning anything? What is the Ext.Ref field, and what is the significance of the value ZBW002342? Should they always enter this value, or are other values possible? Here, we should help the trainee out and teach them something by providing some more information through the use of Custom Text. A better version is shown in the following screenshot: Now the trainee knows exactly what they are entering in the exercise, and understands the business context so they can perform the action correctly when they are doing their actual job. Note that here, we have retained the Template Text (we did not insert the Template Text as Custom Text) which will aid in the translation (although the custom text will still need to be manually translated). We simply added the first paragraph that you see in the Bubble above as Custom Text, and positioned it before the Template Text (the Show custom text first button () is selected by default; you can deselect this if required, to have the Template Text displayed first, but for our purposes we want the Custom Text first).   UPK will run the Template Text in the next line immediately after the Custom Text, so you need to insert an extra line break at the end of the Custom Text if you want the two texts to appear as separate paragraphs. In this example, note that we have continued the scenario that we described in the Introduction pane through into this exercise, by mentioning the customer's name. Again, it is always useful to use a scenario so that the trainee can better relate the exercise to their actual jobs. Note that the text For this exercise...is ZBW002342 will need to be tagged to appear only in See It! and Try It! modes. UPK will run the Template Text in the next line immediately after the Custom Text, so you need to insert an extra line break at the end of the Custom Text if you want the two texts to appear as separate paragraphs. In this example, note that we have continued the scenario that we described in the Introduction pane through into this exercise, by mentioning the customer's name. Again, it is always useful to use a scenario so that the trainee can better relate the exercise to their actual jobs. Note that the text For this exercise...is ZBW002342 will need to be tagged to appear only in See It! and Try It! modes. Whenever practical, you should try to provide some more information, whether this is business context, or a continuation of the scenario you are using, even if this is on every Frame. If you intend for your simulations being used outside of a classroom environment, then you should consider providing exactly the same level of information as the instructor would provide in a classroom. Think about what you would say to the trainee, what additional information or guidance you would give them if you sat next to them, talking them through the simulation, and then add that information into the Bubbles as Custom Text. Remember: training is the effective transfer of knowledge, and if that knowledge is incomplete, then the trainees have not been adequately trained. The End Frame The End Frame is always displayed as the final Frame in the simulation. There is no End Frame equivalent of the Show introduction text option to avoid having this Frame displayed. This is a good thing, as it means that we can use this Frame to provide some final information to the user. This should be seen as a companion to the Start Frame, and should confirm the information presented in the Start Frame. In the Start Frame above, we told the trainee what they would learn. In the End Frame, we should confirm that they have learned this. (This much is standard training theory.) If you have described a scenario in the Start Frame, and followed this through the Action Frames, then you should make reference to this, as well. Suitable End Frame text for our ongoing exercise on SAP user options could be: Although the scenario information is again fairly spurious in this example, it does at least give you an idea of the kind of information that can usefully be included in the End Frame. Again, this information should be tagged for See It! and Try It! modes only. Note that in this example we have also included a message of You have now completed this exercise. This is a nice courtesy, and confirms to the trainee that they have reached the end of the simulation.
Read more
  • 0
  • 0
  • 3854

article-image-create-quick-application-cakephp-part-1
Packt
17 Nov 2009
9 min read
Save for later

Create a Quick Application in CakePHP: Part 1

Packt
17 Nov 2009
9 min read
The ingredients are fresh, sliced up, and in place. The oven is switched on, heated, and burning red. It is time for us to put on the cooking hat, and start making some delicious cake recipes. So, are you ready, baker? In this article, we are going to develop a small application that we'll call the "CakeTooDoo". It will be a simple to-do-list application, which will keep record of the things that we need to do. A shopping list, chapters to study for an exam, list of people you hate, and list of girls you had a crush on are all examples of lists. CakeTooDoo will allow us to keep an updated list. We will be able to view all the tasks, add new tasks, and tick the tasks that are done and much more. Here's another example of a to-do list, things that we are going to cover in this article: Make sure Cake is properly installed for CakeTooDoo Understand the features of CakeTooDoo Create and configure the CakeTooDoo database Write our first Cake model Write our first Cake controller Build a list that shows all the tasks in CakeTooDoo Create a form to add new tasks to CakeTooDoo Create another form to edit tasks in the to-do list Have a data validation rule to make sure users do not enter empty task title Add functionality to delete a task from the list Make separate lists for completed and pending Tasks Make the creation and modification time of a task look nicer Create a homepage for CakeTooDoo Making Sure the Oven is Ready Before we start with CakeTooDoo, let's make sure that our oven is ready. But just to make sure that we do not run into any problem later, here is a check list of things that should already be in place: Apache is properly installed and running in the local machine. MySQL database server is installed and running in the local machine. PHP, version 4.3.2 or higher, is installed and working with Apache. The latest 1.2 version of CakePHP is being used. Apache mod_rewrite module is switched on. AllowOverride is set to all for the web root directory in the Apache configuration file httpd.conf. CakePHP is extracted and placed in the web root directory of Apache. Apache has write access for the tmp directory of CakePHP. In this case, we are going to rename the Cake directory to it CakeTooDoo. CakeTooDoo: a Simple To-do List Application As we already know, CakeTooDoo will be a simple to-do list. The list will consist of many tasks that we want to do. Each task will consist of a title and a status. The title will indicate the thing that we need to do, and the status will keep record of whether the task has been completed or not. Along with the title and the status, each task will also record the time when the task has been created and last modified. Using CakeTooDoo, we will be able to add new tasks, change the status of a task, delete a task, and view all the tasks. Specifically, CakeTooDoo will allow us to do the following things: View all tasks in the list Add a new task to the list Edit a task to change its status View all completed tasks View all pen Delete a task A homepage that will allow access to all the features. You may think that there is a huge gap between knowing what to make and actually making it. But wait! With Cake, that's not true at all! We are just 10 minutes away from the fully functional and working CakeTooDoo. Don't believe me? Just keep reading and you will find it out yourself. Configuring Cake to Work with a Database The first thing we need to do is to create the database that our application will use. Creating database for Cake applications are no different than any other database that you may have created before. But, we just need to follow a few simple naming rules or conventions while creating tables for our database. Once the database is in place, the next step is to tell Cake to use the database. Time for Action: Creating and Configuring the Database Create a database named caketoodoo in the local machine's MySQL server. In your favourite MySQL client, execute the following code: CREATE DATABASE caketoodoo; In our newly created database, create a table named tasks, by running the following code in your MySQL client: USE caketoodoo; CREATE TABLE tasks ( id int(10) unsigned NOT NULL auto_increment, title varchar(255) NOT NULL, done tinyint(1) default NULL, created datetime default NULL, modified datetime default NULL, PRIMARY KEY (id) ); Rename the main cake directory to CakeTooDoo, if you haven't done that yet. Move inside the directory CakeTooDoo/app/config. In the config directory, there is a file named database.php.default. Rename this file to database.php. Open the database.php file with your favourite editor, and move to line number 73, where we will find an array named $default. This array contains database connection options. Assign login to the database user you will be using and password to the password of that user. Assign database to caketoodoo. If we are using the database user ahsan with password sims, the configuration will look like this: var $default = array( 'driver' => 'mysql', 'persistent' => false, 'host' => 'localhost', 'port' => '', 'login' => 'ahsan', 'password' => 'sims', 'database' => 'caketoodoo', 'schema' => '', 'prefix' => '', 'encoding' => '' ); Now, let us check if Cake is being able to connect to the database. Fire up a browser, and point to http://localhost/CakeTooDoo/. We should get the default Cake page that will have the following two lines: Your database configuration file is present and Cake is able to connect to the database, as shown in the following screen shot. If you get the lines, we have successfully configured Cake to use the caketoodoo database. What Just Happened? We just created our first database, following Cake convention, and configured Cake to use that database. Our database, which we named caketoodoo, has only one table named task. It is a convention in Cake to have plural words for table names. Tasks, users, posts, and comments are all valid names for database tables in Cake. Our table tasks has a primary key named id. All tables in Cake applications' database must have id as the primary key for the table. Conventions in CakePHPDatabase tables used with CakePHP should have plural names. All database tables should have a field named id as the primary key of the table. We then configured Cake to use the caketoodoo database. This was achieved by having a file named database.php in the configuration directory of the application. In database.php, we set the default database to caketoodoo. We also set the database username and password that Cake will use to connect to the database server. Lastly, we made sure that Cake was able to connect to our database, by checking the default Cake page. Conventions in Cake are what make the magic happen. By favoring convention over configuration, Cake makes productivity increase to a scary level without any loss to flexibility. We do not need to spend hours setting configuration values to just make the application run. Setting the database name is the only configuration that we will need, everything else will be figured out "automagically" by Cake. Throughout this article, we will get to know more conventions that Cake follows.   Writing our First Model Now that Cake is configured to work with the caketoodoo database, it's time to write our first model. In Cake, each database table should have a corresponding model. The model will be responsible for accessing and modifying data in the table. As we know, our database has only one table named tasks. So, we will need to define only one model. Here is how we will be doing it: Time for Action: Creating the Task Model Move into the directory CakeTooDoo/app/models. Here, create a file named task.php. In the file task.php, write the following code: <?php class Task extends AppModel { var $name = 'Task'; } ?> Make sure there are no white spaces or tabs before the <?php tag and after the ?> tag. Then save the file. What Just Happened? We just created our first Cake model for the database table tasks. All the models in a CakePHP application are placed in the directory named models in the app directory. Conventions in CakePHP: All model files are kept in the directory named models under the app directory. Normally, each database table will have a corresponding file (model) in this directory. The file name for a model has to be singular of the corresponding database table name followed by the .php extension. The model file for the tasks database table is therefore named task.php. Conventions in CakePHP: The model filename should be singular of the corresponding database table name. Models basically contain a PHP class. The name of the class is also singular of the database table name, but this time it is CamelCased. The name of our model is therefore Task. Conventions in CakePHP: A model class name is also singular of the name of the database table that it represents. You will notice that this class inherits another class named AppModel. All models in CakePHP must inherit this class. The AppModel class inherits another class called Model. Model is a core CakePHP class that has all the basic functions to add, modify, delete, and access data from the database. By inheriting this class, all the models will also be able to call these functions, thus we do not need to define them separately each time we have a new model. All we need to do is to inherit the AppModel class for all our models. We then defined a variable named $name in the Task'model, and assigned the name of the model to it. This is not mandatory, as Cake can figure out the name of the model automatically. But, it is a good practice to name it manually.
Read more
  • 0
  • 0
  • 3547

article-image-papervision3d-external-models-part-2
Packt
17 Nov 2009
7 min read
Save for later

Papervision3D External Models: Part 2

Packt
17 Nov 2009
7 min read
Creating and loading models using SketchUp SketchUp is a free 3D modeling program, which was acquired by Google in 2006. It has been designed as a 3D modeling program that's easier to use than other 3D modeling programs. A key to its success is its easy learning curve compared to other 3D tools. Mac OS X, Windows XP, and Windows Vista operating systems are supported by this program that can be downloaded from http://sketchup.google.com/. Although there is a commercial SketchUp Pro version available, the free version works fine in conjunction with Papervision3D. An interesting feature for non-3D modelers is the integration with Google's 3D Warehouse. This makes it possible to search for models that have been contributed by other SketchUp users. These models are free of any rights and can be used in commercial (Papervision3D) projects. Exporting a model from Google's 3D Warehouse for Papervision3D There are several ways to load a model, coming from Google 3D Warehouse, into Papervision3D. One of them is by downloading a SketchUp file and exporting it to a format Papervision3D works with. This approach will be explained. The strength of Google 3D Warehouse is also its weakness. Anybody with a Google account can add models to the warehouse. This results in a variety of quality of the models. Some are very optimized and work fluently, whereas others reveal problems when you try to make them work in Papervision3D. Or they may not work at all, as they're made of too many polygons to run in Papervision3D. Take this into account while searching for a model in the 3D warehouse. For our example we're going to export a picnic table that was found on Google 3D Warehouse. Start Sketch Up. Choose a template when prompted. This example uses Simple Template – Meters, although there shouldn't be a problem with using one of the other templates. Go to File | 3D Warehouse | Get models to open 3D Warehouse inside SketchUp. Enter a keyword to search for. In this example that will be picnic table. Select a model of your choice. Keep in mind that it has to be low poly, which is something you usually find out by trial and error. Click on Download Model, to import the model into SketchUp and click OK when asked if you want to load the model directly into your Google SketchUp model. Place the model at the origin of the scene. To follow these steps, it doesn't have to be the exact origin, approximately is good enough. By default, a 2D character called Sang appears in the scene, which you do not necessarily have to remove; it will be ignored during export. Because the search returned a lot of picnic tables varying in quality, there is a SketchUp file (can be downloaded from http://www.packtpub.com/files/code/5722_Code.zip). This file has a picnic table already placed on the origin. Of course, you could also choose another picnic table, or any other object of your choice. Leave the model as it is and export it. Go to File | Export | 3D Model. Export it using the Google Earth (*.kmz) format and save it in your assets folder. T he file format we're exporting to originally was meant to display 3D objects in Google Earth. The file ends with .kmz as its extension, and is actually a ZIP archive that contains a COLLADA file and the textures. In the early days of Papervision3D, it was a common trick to create a model using SketchUp and then get the COLLADA file out of the exported Google Earth file, as the Google Earth KMZ file format wasn't still supported then. Importing a Google Earth model into Papervision3D Now that we have successfully exported a model from SketchUp, we will import it into Papervision3D. This doesn't really differ from loading a COLLADA or 3D Studio file. The class we use for parsing the created PicnicTable.kmz file is called KMZ and can be found in the parsers package. Add the following line to the import section of your document class: import org.papervision3d.objects.parsers.KMZ; Replace or comment the code that loads the animated COLLADA model and defines the animations from the previous example. In the init() method we can then instantiate the KMZ class, assign it to the model class property, and load the KMZ file. Make sure you have saved PicnicTable.kmz file into the assets folder of your project. model = new KMZ();model.addEventListener(FileLoadEvent.LOAD_COMPLETE,modelLoaded);KMZ(model).load("assets/PicnicTable.kmz"); ExternalModelsExample That looks familiar, right? Now let's publish the project and your model should appear on the screen. Notice that in many cases, the downloaded and exported model from Google 3D Warehouse might appear very small on your screen in Papervision3D. This is because they are modeled with other metric units than we use in Papervision3D. Our example application places the camera at a 1000 units away from the origin of the scene. Many 3D Warehouse models are made using units that define meters or feet, which makes sense if you were to translate them to real-world units. When a model is, for example, 1 meter wide in SketchUp, this equals to 1 unit in Papervision3D. As you can imagine, a 1 unit wide object in Papervision3D will barely be visible when placing the camera at a distance of 1000. To solve this you could use one of the following options: Use other units in Papervision3D and place your camera at a distance of 5 instead of 1000. Usually you can do this at the beginning of your project, but not while the project is already in progress, as this might involve a lot of changes in your code due to other objects, animations, and calculations that are made with a different scale. Scale your model inside SketchUp to a value that matches the units as you use them in Papervision3D. When the first option can't be realized, this option is recommended. Scale the loaded model in Papervision3D by changing the scale property of your model. model.scale = 20; Although this is an option that works, it's not recommended. Papervision3D has some issues with scaled 3D objects at the time of writing. It is a good convention to use the same units in Papervision3D and your 3D modeling tool. If you want to learn more about modeling with SketchUp, visit the support page on the SketchUp web site http://sketchup.google.com/support. You'll find help resources such as video tutorials and a help forum. Creating and loading models using Blender Blender is an open source, platform-independent 3D modeling tool, which was first released in 1998 as a shareware program. It went open source in 2002. Its features are similar to those in commercial tools such as 3ds Max, Maya, and Cinema4D. However, it has a reputation of having a difficult learning curve compared to other 3D modeling programs. Blender is strongly based on usage of keyboard shortcuts and not menus, which makes it hard for new users to find the options they're looking for. In the last few years, more menu-driven interfaces have been added. It's not in the scope of this article to teach you everything about the modeling tools that can be used with Papervision3D. This also counts for Blender. There are many resources such as online tutorials and books that cover how to work with Blender. A link to the Blender installer download can be found on its web site: www.blender.org.
Read more
  • 0
  • 0
  • 2020
article-image-glossary-upk-35
Packt
16 Nov 2009
9 min read
Save for later

Glossary in UPK 3.5

Packt
16 Nov 2009
9 min read
A Glossary in UPK effectively associates Web Pages to key terms. UPK will then turn any instance of that key term within a Topic (or other content object) to which that Glossary is assigned into a hyperlink to the associated Web Page. You could do this manually by creating Web Pages (as explained above) and then creating hyperlinks to these manually, but the advantage of using the Glossary functionality is that UPK will 'automatically' find each occurrence of the glossary term and create the hyperlink. I say 'automatically' in quotation marks, as UPK does not do this completely automatically. You have to tell it to go and create the hyperlinks, but once you tell it to do so, it will dutifully go off and find 'every' occurrence of all of the terms in the glossary and create the hyperlinks to the definition. And I say 'every' in quotation marks, because UPK won't necessarily link every instance of the term. UPK allows you to choose (at the Library level) whether it turns literally every occurrence of the term found in a location into a hyperlink, or only the first occurrence. A location here is effectively a single Bubble or Web Page. One could well ask why UPK doesn't provide the option to create a Glossary link only for the first occurrence of the term in a Topic. The simple answer is that it does not necessarily know which occurrence is the first, because the Topic could include Alternative Paths. So UPK takes an over-cautious approach, and considers each block of information separately. This option is specified in the Options panel (menu option Tools|Options), under the Content Defaults|Glossary section, as shown in the following screenshot: Creating a glossary To create a Glossary in UPK, follow these steps: From the main Library screen, click on the folder within which you want to create the Glossary. You can create the Glossary in any folder, but it makes sense to have a single folder that contains the Glossary file itself, and all of the Web Pages used for the terms. Select menu option File|New|Glossary. The Glossary Editor is opened in a new tab within the UPK Developer window, as shown in the following screenshot: To create glossary entries from within the Glossary, follow these steps: Enter the glossary term in the first free Glossary Term field. Click in the Definition Link field on the same line. An ellipsis (...) is displayed on the rightmost side of the field. Click on the ellipsis. The Edit Definition Link dialog box is displayed. Click on the Create New Web Page field. The Save As dialog box is displayed. Navigate to the directory in which you want to save the Glossary definition. Again, it makes sense to save these in the same folder as the Glossary itself. The Name field will default to the glossary term specified in the Glossary Term field. It is recommended that you keep this default, and use the definition term as the Web Page name. You could use any other name, if you wanted to (UPK does not use this name to locate the definition; it uses its own, internal identifier) but using the same name will allow you to easily locate the term Web Page if you need to. Click on the Save button. A new tab is opened for the Web Page. Enter the Glossary description into this page. The Web Page will use the default font and colors. You can override these defaults, if required. Close the Web Page by clicking on the x on the rightmost side of the open tab list. You are passed back to the Glossary Editor. Enter a suitable ToolTip text for the glossary entry in the Tooltip field. This text will be displayed when the user hovers the mouse over the hyperlinked glossary term. If only whole instances of the glossary term should be turned into hyperlinks (for example, if the term is Order then "Orders" and "Ordering" will not be hyperlinked), then select the Match Whole Word option. Otherwise, make sure that this option is not selected. If only text that matches the case of the term should be turned into hyperlinks (for example, if the term is Order then "order" will not be hyperlinked), then select the Match Case field. Otherwise, make sure that this option is not selected. Repeat Steps 1 to 14 for all additional terms that you want to add to the Glossary. To create a glossary entry that uses an existing Web Page for the glossary term, follow these steps: Enter the glossary term in the first free Glossary Term field. Click in the Definition Link field on the same line. An ellipsis (...) is displayed on the rightmost side of the field. Click on the ellipsis. The Edit Definition Link dialog box is displayed. Click on the Create Link button. The Insert Hyperlink dialog box is displayed. Navigate to, and select, the Web Page that contains the glossary description. Click on OK. The Edit Definition Link dialog box is redisplayed. You can edit the Web Page directly from this dialog box, by clicking on the Edit Web Page icon. Click on OK. You are returned to the Glossary tabbed page. Once you have defined all required Glossary entries, save and close the Glossary, by following the steps shown below: Click the Save button to save your changes to the Glossary. The Save As dialog box is displayed. Navigate to the directory in which you want to save the Glossary. Enter a suitable name for the Glossary in the Name field. Close the Glossary Editor by clicking on the x on the rightmost side of the open tab list. An example of a partially-populated Glossary is shown in the next screenshot: You can see from the Definition Link column above that all of the Glossary definition files are stored in the same, single folder, called Glossary. This is the same folder that the Glossary object itself is stored in. Personally, I find it useful to keep all of the content objects for the Glossary in the same single folder. This is not strictly necessary, but it does keep things organized. You will also note that the Tooltip is the same in every case. I tend to always use the tooltip Glossary so that the user knows that the hyperlink links to the Glossary, and not to another form of Web Page. Assigning a Glossary to content objects Creating a Glossary is only half the story. You need to manually assign the Glossary to each object that you want to use that Glossary (that is, for which you want the terms specified in the Glossary to be hyperlinked to the Glossary definitions). Version Difference In OnDemand Version 8.7 and earlier, a single Glossary was created for a Title, and was automatically applied to all content objects within that Title. In UPK 3.5, it is possible to have multiple Glossaries within a single Library, so it is necessary to specify which content objects should use which Glossary. This assignment is done via the content object's Properties, as shown in the screenshot below. The Glossary property is available for Modules, Sections, Topics, and Web Pages. This means that you could potentially assign one Glossary to a Module, another Glossary to a Section within this Module, and a third Glossary to a Topic within that Section. Not that you're very likely to want to do this. But you could. The further implication of the requirement to assign a Glossary to each content object individually is that you can define multiple Glossaries, and assign different Glossaries to different objects. I'd question the wisdom of assigning one glossary to a Module, and then an entirely different Glossary to a Topic within that Module, but I can certainly see the benefit of having multiple Glossaries available in a Library that contained (for example) simulations for multiple applications; you could create application-specific Glossaries (as you no doubt do at the moment) and then assign each Glossary to only the content objects for the relevant application. The downside (for those of us who favor modularization and reuse) is that it is only possible to assign a single Glossary to any given object. So it is not possible to, say, create a company-wide Glossary and separate application-specific Glossaries, and then assign the company-wide Glossary and the relevant application-specific glossary to a single Topic. But more resourceful readers will have already worked out how to get around this limitation. Need a clue? Glossary entries are just Web Pages, and any given Web Page can be reused in multiple places. Need more help? A Web Page can be included in more than one Glossary. So you should first define Web Pages for all of your Glossary terms. You can then create multiple Glossaries (for example, one for each application), and include whichever terms' Web Pages you need to in each of the glossaries. If a term applies to two applications, then simply include the Web Page for that term in the Glossaries for both applications. Simple! Of course there is some slight duplication of effort as you need to create the entry in the actual Glossary content object twice (once in each glossary), but you are reusing the individual definitions, so it could be worse. Unfortunately, unlike Templates, it is not possible to specify a default Glossary in your user defaults. This means that this assignment must be done separately for each content object. However, there are a couple of shortcuts that UPK provides which avoid the need to assign the Glossary to content objects one by one. First, if you select multiple content objects (Modules, Sections, Topics, or Web Pages) and display the Properties pane, then you can assign the Glossary to all of the selected objects in one fell swoop. Second, if you assign a Glossary to a Module, then any new Sections or Titles that you create within that Module – that is, from within the Outline Editor – will inherit this Glossary assignment. However, it is important to note that this will only apply to new content objects. If you assign a Glossary to an outline element and then insert pre-existing content objects into this outline element, then these pre-existing content objects will not inherit the Glossary assignment. Regenerating the Glossary links Glossary links are not created (or updated) automatically. You need to tell UPK to go and search through your content objects and turn any instances of the Glossary terms into links to the Glossary definitions. This is good in that you can at least have control over when it does this, but bad in that it is easy to forget to do so.
Read more
  • 0
  • 0
  • 1713

article-image-session-and-user-joomla-15-part-2
Packt
16 Nov 2009
8 min read
Save for later

The Session and the User with Joomla! 1.5: Part 2

Packt
16 Nov 2009
8 min read
Getting the user's group ID and type The following organized list describes the user groups in Joomla! 1.5 in a way in which we are all probably familiar. Each of these groups has an ID and a name; these are the group's ID and type respectively. This recipe explains how to find the group ID and group type of the current user. Note that the hard coded group IDs should not generally be used for access control; for this it is best to take advantage of the JAuthorization class. For more information, refer to the Joomla! API site: http://api.joomla.org/. Dynamic permissions There is no administrative functionality that allows the modification of groups. It is, however, possible to programmatically modify groups and group permissions. Most of the time a Joomla! 1.5 extension will opt to implement its own permission management rather than use the incomplete Joomla! solution. Joomla! 1.6, in development at the time of writing, promises a complete GACL (Group Access Control List) implementation. Getting ready To complete this recipe, we need the JUser object that represents the current user. For more information, refer to the Getting the user recipe covered earlier in this article. $user =& JFactory::getUser(); How to do it... The group ID is held in the gid field in the #__users table. We use the JUser::get() method to extract the user's group ID. // determine group ID$groupID = $user->get('gid'); The group type is held in the usertype field in the #__users table. Again, we use the JUser::get() method to extract the user's group ID. $usertype = $user->get('usertype'); How it works... Apart from the obvious format of the data, there is a subtle difference between the gid and usertype fields. The gid field is always populated, where as the usertype field is populated only if the user is not blocked. When dealing with the current user, the user will never be blocked, but when dealing with other users there is a possibility that the usertype field will not be populated. Therefore, we need to be careful that we select the most appropriate field, depending on the context of what we are doing. The user groups that we have mentioned are maintained in the #__core_acl_aro_groups table, or as it can also be called, the Access Control List Access Request Object Groups table. This table holds a tree structure, as indicated in the introduction of the recipe. What is noticeable is that Public Frontend and Public Backend are both technically user groups. As this is a tree structure, we can use the left and right values to perform all sorts of neat tricks. The #__core_acl_aro_groups table employs the nested set model: http://dev.mysql.com/tech-resources/articles/hierarchical-data.html. See also The next recipe explains how to use the Public, Registered, and Special access levels. Restricting a user's access using Public, Registered, and Special In Joomla! we often define access based on three simple access levels. These levels relate to the user groups described in the previous recipe, Getting the user's group ID and type. This recipe explains how to use these access levels to restrict access to resources. Public (0) Registered (1) Special (2) Getting ready To complete this recipe, we need the JUser object that represents the current user. For more information, refer to the Getting the user recipe covered earlier in this article. $user =& JFactory::getUser(); How to do it... The JUser::get() method can be used to retrieve the user's aid. This is the access level number, as shown in braces in the recipe introduction. // get access level$aid = $user->get('aid'); For each resource we want to restrict access to, we must define the required access level. This is normally done in a database TINYINT(3) field named access. This can then be used to restrict the access using a database query (used when listing)... // get the DBO$db =& JFactory::getDBO();// prepare access field name$access = $db->nameQuote('access');// restrict query by access level$where = "WHERE $access <= " . intval($aid); ...or when viewing a single record. Note that ALERTNOTAUTH is a core translation string, which in en-GB is equivalent to You are not authorised to view this resource. // make sure the user has the necessary access rightsif ($table->get('access') > $aid) { JError::raiseError(403, JText::_('ALERTNOTAUTH')); jexit();} How it works... A good example of this in action is the content component. If we take a look at the article manager, we can see that there is an Access Level associated with each article that determines which users can access the article. These access levels are relatively primitive and we don't have any bona fide control over them. So how do they translate into concrete user groups? The following table describes the seven user groups and the guest group, and shows how these relate to the access levels: User Group User Group ID Access Level Access Level ID None (Guest) 0 Public 0 Registered 18 Registered 1 Author 19 Special 2 Editor 20 Special 2 Publisher 21 Special 2 Manager 23 Special 2 Administrator 24 Special 2 Super Administrator 25 Special 2 See also The previous recipe explains how to work with the user group ID and group type. This can also be useful for dealing with access control. Getting the user's parameters User parameters provide a mechanism for including additional user data without the need to modify the database. The core user parameters are defined in the XML files located in the administratorcomponentscom_usersmodels folder. Getting ready To complete this recipe, we need the JUser object that represents the current user. For more information, refer to the Getting the user recipe. $user =& JFactory::getUser(); How to do it... To retrieve a parameter we must first know the name of the parameter we want to retrieve. One example of a common parameter is timezone. This is an integer that defines the hours offset from UTC (Coordinated Universal Time), also known as GMT (Greenwich Mean Time) and Z (Zulu). To retrieve a parameter, we can use the JUser::getParam() method. $timezone = $user->getParam('timezone'); It is also possible to provide a default value. This is useful especially if the user we are dealing with is a guest because guest users do not have any parameters defined by default. $timezone = $user->getParam('timezone', '0'); How it works... A user's parameters are represented as a JParameter object. We do not have to interact directly with this object because JUser will do this for us. A minor problem with this method is the default value. Technically the XML files define the default values, but by default these XML files are not loaded by the JUser class. Therefore, the XML defined default values are not employed. One way to overcome this is to interact directly with the JParameter object. The next section explains how to do this. There's more... The JUser::getParamters() method allows us to directly access the user's JParameter object. Note that we must use =& when assigning the return value to a variable. If we do not and we are using a PHP version prior to PHP 5, we will inadvertently create a copy of the returned JParameter object. // get parameters with XML definition file loaded$params =& $user->getParameters(true); When we retrieve the JParameter object there are two optional parameters. The first parameter $loadsetupfile determines whether or not the XML file that defines the user's parameters should be loaded. Loading this file gives meaning to the data and also provides default values for defined data. For information about the second optional parameter, refer to the recipe, Extending and editing user parameters. To retrieve a value, we use the JParameter::get() method. We pass two parameters to this method—the name of the parameter we want the value of, and the default value to return if the parameter does not exist. // get timezone (hours UTC offset)$timezone = $params->get('timezone', '0'); See also The next recipe, Setting the user's parameters, explains how to set a value in the user's parameters. Setting the user's parameters User parameters provide a mechanism for including additional user data without the need to modify the database. The parameters are defined in the XML files located in the administratorcomponentscom_usersmodels folder. User parameters are not restricted to the parameters defined in the XML files. It is perfectly acceptable to add additional parameters. Getting ready To complete this recipe, we need the JUser object that represents the current user. For more information, refer to the Getting the user recipe. $user =& JFactory::getUser(); How to do it... To set the value of a parameter, we use the JUser::setParam() method. This method requires two parameters—the name of the parameter we want to set and the value to which we want to set the parameter. For example, we could change the user's editor preference to use no editor. // set value of someparameter to some value$user->setParam('editor', 'none'); There's more... If we have retrieved the JParamter object directly from the user, we can alternatively use that object to set the user's parameters. For information about retrieving the user's parameters, refer to the previous recipe, Getting the user's parameters. To set data in the JParameter object, we use the JParameter::set() method. // set value of someparameter to some value$params->set('editor', 'none'); See also The previous recipe explains how to get a value from the user's parameters.
Read more
  • 0
  • 0
  • 1604

article-image-session-and-user-joomla-15-part-1
Packt
16 Nov 2009
9 min read
Save for later

The Session and the User with Joomla! 1.5: Part 1

Packt
16 Nov 2009
9 min read
Introduction When a user starts browsing a Joomla! web site, a PHP session is created. Hidden away in the session is user information, this information will either represent a known registered user or a guest. We can interact with the session using the session handler, a JSession object. When we work with the session in Joomla!, we must not use the global PHP $_SESSION variable or any of the PHP session functions. Getting the session handler To interact with the session we use the session handler; this is a JSession object that is globally available via the static JFactory interface. It is imperative that we only use the global JSession object to interact with the PHP session. Directly using $_SESSION or any of the PHP session functions could have unintended consequences. How to do it... To retrieve the JSession object we use JFactory. As JFactory returns an object, we must use =& when assigning the object to a variable. If we do not and our server is running a PHP version prior to PHP 5, we will inadvertently create a copy of the global JSession object. $session =& JFactory::getSession(); How it works... If we look at the JSession class, we will notice that there is a getInstance() method. It is tempting to think of this as synonymous with the JFactory::getSession() method. There is, however, an important difference, the JSession::getInstance() method requires configuration parameters. The JFactory::getSession() method accepts configuration parameters, but they are not required. The first time the JFactory::getSession() method is executed, it is done by the JApplication object (often referred to as mainframe). This creates the session handler. It is the application and JFactory that deal with the configuration of the session. Subsequent usage of the JFactory::getSession() method will not require the creation of the object, and thus simply returns the existing object. The following sequence diagram shows how this process works the first time it is executed by the JApplication object: When the JFactory::getSession() method is subsequently executed, because session will already exist, the _createSession() method is not executed. The diagram is a simplification of the process; additional complexity has not been included because it is outside the scope of this recipe. See also For information about setting and retrieving values in the session, refer to the next two recipes, Adding data to the session and Getting session data. Adding data to the session Data that is set in the session is maintained between client requests. For example, we could display announcements at the top of all our pages and include an option to hide the announcements. Once a user opts to hide the announcements, by setting a value in the session, we would be able to 'remember' this throughout the user's visit. To put this into context, we would set a session value hideAnnouncements to true when a user opts to hide announcements. In subsequent requests, we will be able to retrieve the value of hideAnnouncements from the session and its state will remain the same. In Joomla!, session data is maintained using a JSession object, which we can retrieve using JFactory. This recipe explains how to set data in the session using this object instead of using the global PHP $_SESSION variable. Getting ready We must get the session handler, a JSession object. For more information, refer to the first recipe in this article, Getting the session handler. $session =& JFactory::getSession(); How to do it... The JSession::set() method is used to set a value in the session. The first parameter is the name of the value we want to set; the second is the value itself. $session->set('hideAnnouncements', $value); The JSession::set() method returns the previous value. If no value was previously set, the return value will be null. // set the new value and retrieve the old$oldValue = $session->set('hideAnnouncements', $value);echo 'Hide Announcement was ' . ($oldValue ? 'true' : 'false');echo 'Hide Announcement is now ' . ($value ? 'true' : 'false'); Lastly, we can remove data from the session by setting the value to null. // remove something$session->set('hideAnnouncements', null); How it works... The session contains a namespace-style data structure. Namespaces are required by JSession and by default all values are set in the default namespace. To set a value in a different namespace, we use the optional JSession::set() third parameter. $session->set('something', $value, 'mynamespace'); Sessions aren't just restricted to storing basic values such as strings and integers. The JUser object is a case in point—every session includes an instance of JUser that represents the user the session belongs to. If we add objects to the session, we must be careful. All session data is serialized. To successfully unserialize an object, the class must already be known when the session is restored. For example, the JObject class is safe to serialize because it is loaded prior to restoring the session. $value = new JObject();$session->set('aJObject', $value); If we attempt to do this with a class that is not loaded when the session is restored, we will end up with an object of type __PHP_Incomplete_Class. To overcome this, we can serialize the object ourselves. // serialize the object in the session$session->set('anObject', serialize($anObject)); To retrieve this, we must unserialize the object after we have loaded the class. If we do not do this, we will end up with a string that looks something like this O:7:"MyClass":1:{s:1:"x";s:10:"some value";}. // load the classinclude_once(JPATH_COMPONENT . DS . 'myclass.php');// unserialize the object from the session$value = unserialize($session->get('anObject')); There's more... There is an alternative way of setting data in the session. User state data is also part of the session, but this data allows us to save session data using more complex hierarchical namespaces, for example com_myextension.foo.bar.baz. To access this session data, we use the application object instead of the session handler. // get the application$app =& JFactory::getApplication();// set some data$app->setUserState('com_myextsion.foo.bar.baz, $value); An advantage of using user state data is that we can combine this with request data. For more information refer to the next recipe, Getting session data. The JApplication::setUserState() method is documented as returning the old value. However, a bug prevents this from working; instead the new value is returned. See also For information about retrieving values from the session, refer to the next recipe, Getting session data. Getting session data Data that is set in the session is maintained between client requests. For example if during one request we set the session value of hideAnnouncements to true, as described in the previous recipe, in subsequent requests we will be able to retrieve the value of hideAnnouncements and its state will remain the same. In Joomla!, session data is maintained using the global JSession object. This recipe explains how to get data from the session using this object instead of from the normal global PHP $_SESSION variable. Getting ready We must get the session handler, a JSession object. For more information, refer to the first recipe in this article, Getting the session handler. $session =& JFactory::getSession(); How to do it... We use the JSession::get() method to retrieve data from the session. $value = $session->get('hideAnnouncements'); If the value we attempt to retrieve is not set in the session, the value null is returned. It is possible to specify a default value, which will be returned in instances where the value is not currently set in the session. $defaultValue = false;$value = $session->get('hideAnnouncements', $defaultValue); How it works... The session contains a namespace-style data structure. Namespaces are required by JSession and by default all values are retrieved from the default namespace. To get a value from a different namespace, we use the optional third JSession::get() parameter. $value = $session->get('hideAnnouncements', $defaultValue, 'mynamespace'); It is possible to store objects in the session. However, these require special attention when we extract them from the session. For more information about storing objects in the session, refer to the previous recipe, Adding data to the session. There's more... There is an alternative way of getting data from the session. User state data is also part of the session. The user state data allows us to store session data using more complex hierarchical namespaces, for example com_myextension.foo.bar.baz. To access user state data, we use the application object instead of the session handler. // get the application (this is the same as $mainframe)$app =& JFactory::getApplication();// get some user state data$value = $app->getUserState('com_myextsion.foo.bar.baz'); User state data is usually combined with request data. For example, if we know the request may include a value that we want to use to update the user state data, we use the JApplication::getUserStateFromRequest() method. // get some user state data and update from request$value = $app->getUserStateFromRequest( 'com_myextsion.foo.bar.baz', 'inputName', $defaultValue, 'INTEGER'); The four parameters we provide this method with are the path to the value in the state data, the name of the request input from which we want to update the value, the default value (which is used if there is no value in the request), and the type of value. This method is used extensively for dealing with display state data, such as pagination. // get global default pagination limit$defaultListLimit = $app->getCfg('list_limit');// get limit based on user state data / request data$limit = $app->getUserStateFromRequest( 'global.list.limit', 'limit', $defaultListLimit, 'INTEGER'); See also For information about setting values in the session, refer to the previous recipe, Adding data to the session.
Read more
  • 0
  • 0
  • 3337
article-image-apache-geronimo-logging
Packt
16 Nov 2009
8 min read
Save for later

Apache Geronimo Logging

Packt
16 Nov 2009
8 min read
We will start by briefly looking at each of the logging frameworks mentioned above, and will then go into how the server logs events and errors and where it logs them to. After examining them, we will look into the different ways in which we can configure application logging. Apache log4j: Log4j is an open source logging framework that is developed by the Apache Software Foundation. It provides a set of loggers, appenders, and layouts, to control which messages should be logged at runtime, where they should be logged to, and in what format they should be logged. The loggers are organized in a tree hierarchy, starting with the root logger at the top of the hierarchy. All loggers except the root logger are named entities and can be retrieved by their names. The root logger can be accessed by using the Logger.getRootLogger() API, while all other loggers can be accessed by using the Logger.getLogger() API. The names of the loggers follow the rule that the name of the parent logger followed by a '.' is a prefix to the child logger's name. For example, if com.logger.test is the name of a logger, then its direct ancestor is com.logger, and the prior ancestor is com. Each of the loggers may be assigned levels. The set of possible levels in an ascending order are—TRACE, DEBUG, INFO, WARN, ERROR, and FATAL. If a logger is not assigned a level, then it inherits its level from its closest ancestor. A log statement makes a logging request to the log4j subsystem. This request is enabled only if its logging level is higher than or equal to its logger's level. If it is lower than the log, then the message is not output through the configured appenders. Log4j allows logs to be output to multiple destinations. This is done via different appenders. Currently there are appenders for the console, files, GUI components, JMS destinations, NT, and Unix system event loggers and remote sockets. Log4j is one of the most widely-used logging frameworks for Java applications, especially ones running on application servers. It also provides more features than the other logging framework that we are about to see, that is, the Java Logging API. Java Logging API: The Java Logging API, also called JUL, from the java.util.logging package name of the framework, is another logging framework that is distributed with J2SE from version 1.4 onwards. It also provides a hierarchy of loggers such as log4j, and the inheritance of properties by child loggers from parents just like log4j. It provides handlers for handling output, and formatters for configuring the way that the output is displayed. It provides a subset of the functionality that log4j provides, but the advantage is that it is bundled with the JRE, and so does not require the application to include third-party JARS as log4j does. SLF4J: The Simple Logging Facade for Java or SLF4J is an abstraction or facade over various logging systems. It allows a developer to plug in the desired logging framework at deployment time. It also supports the bridging of legacy API calls through the slf4j API, and to the underlying logging implementation. Versions of Apache Geronimo prior to 2.0 used Apache Commons logging as the facade or wrapper. However, commons logging uses runtime binding and a dynamic discovery mechanism, which came to be the source of quite a few bugs. Hence, Apache Geronimo migrated to slf4j, which allows the developer to plug in the logging framework during deployment, thereby eliminating the need for runtime binding. Configuring Apache Geronimo logging Apache Geronimo uses slf4j and log4j for logging. The log4j configuration files can be found in the <GERONIMO_HOME>/var/log directory. There are three configuration files that you will find in this directory, namely: client-log4j.properties deployer-log4j.properties server-log4j.properties Just as they are named, these files configure log4j logging for the client container (Java EE application client), deployer system, and the server. You will also find the corresponding log files—client.log, deployer.log, and server.log. The properties files, listed above, contain the configuration of the various appenders, loggers, and layouts for the server, deployer, and client. As mentioned above, log4j provides a hierarchy of loggers with a granularity ranging from the entire server to each class on the server. Let us examine one of the configuration files: the server-log4j.properties file: This file starts with the line log4j.rootLogger=INFO, CONSOLE, FILE. This means that the log4j root logger has a level of INFO and writes log statements to two appenders, namely, the CONSOLE appender and the FILE appender. These are the appenders that write to the console and to files respectively. The console appender and file appenders are configured to write to System.out and to <GERONIMO_HOME>/var/log/geronimo.log. Below this section, there is a finer-grained configuration of loggers at class or package levels. For example, log4j.logger.openjpa.Enhance=TRACE. It configures the logger for the class log4j.logger.openjpa.Enhance to the TRACE level. Note that all of the classes that do not have a log level defined will take on the log level of their parents. This applies recursively until we reach the root logger and inherit its log level (INFO in this case). Configuring application logging We will be illustrating how applications can log messages in Geronimo by using two logging frameworks, namely, log4j and JUL. We will also illustrate how you can use the slf4j wrapper to log messages with the above two underlying implementations. We will be using a sample application, namely, the HelloWorld web application to illustrate this. Using log4j We can use log4j for logging the application log to either a separate logfile or to the geronimo.log file. We will also illustrate how the logs can be written to a separate file in the <GERONIMO_HOME>/var/log directory, by using a GBean. Logging to the geronimo.log file and the command console Logging to the geronimo.log file and the command console is the simplest way to do application logging in Geronimo. For enabling this in your application, you only need to add logging statements to your application code. The HelloWorld sample application has a servlet called HelloWorldServlet, which has the following statements for enabling logging. The servlet is shown below. package com.packtpub.hello;import java.io.*;import javax.servlet.ServletException;import javax.servlet.http.*;import org.apache.log4j.Logger;public class HelloWorldServlet extends HttpServlet{ Logger logger = Logger.getLogger(HelloWorldServlet.class.getName()); protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType("text/html"); PrintWriter out = res.getWriter(); out.print("<html>"); logger.info("Printing out <html>"); out.print("<head><title>Hello World Application</title></head>"); logger.info("Printing out <head><title>Hello World Application</title></head>"); out.print("<body>"); logger.info("Printing out <body>"); out.print("<b>Hello World</b><br>"); logger.info("Printing out <b>Hello World</b><br>"); out.print("</body>"); logger.info("Printing out </body>"); out.print("</html>"); logger.info("Printing out </html>"); logger.warn("Sample Warning message"); logger.error("Sample error message"); }} Deploy the sample HelloWorld-1.0.war file, and then access http://localhost:8080/HelloWorld/. This servlet will log the following messages in the command console, as shown in the image below: The geronimo.log file will have the following entries: 2009-02-02 20:01:38,906 INFO [HelloWorldServlet] Printing out <html>2009-02-02 20:01:38,906 INFO [HelloWorldServlet] Printing out <head><title>Hello World Application</title></head>2009-02-02 20:01:38,906 INFO [HelloWorldServlet] Printing out <body>2009-02-02 20:01:38,906 INFO [HelloWorldServlet] Printing out <b>Hello World</b><br>2009-02-02 20:01:38,906 INFO [HelloWorldServlet] Printing out </body>2009-02-02 20:01:38,906 INFO [HelloWorldServlet] Printing out </html>2009-02-02 20:01:38,906 WARN [HelloWorldServlet] Sample Warning message2009-02-02 20:01:38,906 ERROR [HelloWorldServlet] Sample error message Notice that only the messages with a logging level of greater than or equal to WARN are being logged to the command console, while all the INFO, ERROR, and WARN messages are logged to the geronimo.log file. This is because in server-log4j.properties the CONSOLE appender's threshold is set to the value of the system property, org.apache.geronimo.log.ConsoleLogLevel, as shown below: log4j.appender.CONSOLE.Threshold=${org.apache.geronimo.log.ConsoleLogLevel} The value of this property is, by default, WARN. All of the INFO messages are logged to the logfile because the FILE appender has a lower threshold, of TRACE, as shown below: log4j.appender.FILE.Threshold=TRACE Using this method, you can log messages of different severity to the console and logfile to which the server messages are logged. This is done for operator convenience, that is, only high severity log messages, such as warnings and errors, are logged to the console, and they need the operator's attention. The other messages are logged only to a file.
Read more
  • 0
  • 0
  • 2894

article-image-using-web-pages-upk-35
Packt
16 Nov 2009
12 min read
Save for later

Using Web Pages in UPK 3.5

Packt
16 Nov 2009
12 min read
Using Web Pages in the Concept pane The most common use of Web Pages is to provide context information for Topics. Look at the following image of the Outline for our example course: You will see that the upper-right section of the Outline window contains a pane labeled Concept. If you want any information to be displayed in this pane, then you need to create a Web Page and attach it to the content object in the Outline. Version Difference Although the Concept pane always appears in the Outline view, if it is empty, then it does not appear in the Player. This is, thankfully, a new feature in UPK 3.5. In previous versions, the Concept pane always appeared in the Player, often appearing as a blank frame, where developers couldn't be bothered to provide any concepts. To create a new Web Page and attach it to a content object, carry out the following steps: Open the Outline containing the content object to which you want to attach the Web Page, in the Outline Editor. Click on the content object to select it. Although in this example we are attaching a Web Page to the concept pane for a Topic, Modules, and Sections also have concept panes, so you can also attach Web Pages to these as well. Click on the Create new web page button () in the Concept pane. The Save As dialog box is displayed. Navigate to the folder in which you want to save the Web Page (we will use an Assets sub-folder for all of our Web Pages), enter a name for the Web Page (it makes sense to use a reference to the content object to which the Web Page relates), and then click on the Save button. The Web Page Editor is opened on the Developer screen, as shown in the next screenshot: Enter the information that you want to appear in the Concept pane in the editor (as has already been done in the previous example). You can set the font face and size; set text to bold, italics, and underlined; change the text color, and change change the background color (again, as has been done in the earlier example). You can also change the paragraph alignment, and format numbered and bulleted lists. Once you have entered the required text, click on the Save button () to save your changes, and then close the Web Page Editor tabbed page. You are returned to the Outline Editor. Now, the Concept pane shows the contents of the Web Page, as shown in the next screenshot: Version Difference In UPK 3.5 (and OnDemand 9.1) you can only attach a single Web Page to the Concept pane. This is a change from OnDemand 8.7, where you could attach multiple Infoblocks and they would be displayed sequentially. (But note that if you convert content from OnDemand 8.7 where there are multiple Infoblocks in a single Concept pane, then all of the attached Infoblocks will be converted to a single Web Page in UPK 3.5.) The above steps explain how to attach a Web Page to the Concept pane for an exercise from the Outline. Although this is done from the Outline, the Web Page is attached to the Topic content object and not to the outline element. If you subsequently insert the same Topic in another Outline, the same Web Page will be used in the Concept pane of the new Outline. You can also attach a Web Page to the Concept pane for an exercise from within the Topic Editor. This is a useful option if you want to create concept Web Page but have not yet built an Outline to house the Topic. To do this, follow these steps: Open the Topic in the Topic Editor. Select menu option View|Concept Properties. The Concept Properties dialog box is displayed. This is very similar to the Concept pane seen in the Overview. It contains the same buttons. Create and save the Web Page as described above. When you have finished, and the Web Page is displayed in the Concept Properties dialog box, click on the OK button. Using images in Web Pages As stated above, a Web Page can contain an image. This can be instead of, or in addition to, any text (although if you only wanted to include a single image in the Web Page you could always use a Package, as explained later in this article). Images are a nice way of adding interest to a Web Page (and therefore to your training), or of providing additional information that is better explained graphically (after all, a picture is worth a thousand words). However, if you are using images in the Concept pane, then you should consider the overall size of the image and the likely width of the Concept pane, bearing in mind that the trainee may run the Player in a smaller window than the one you design. For our sample exercise, given that we are providing simulations for the SAP system, we will include a small SAP logo in the Web Page that appears in the Concept pane for our course Module. For the sake of variety, we will do this from the Library, and not from the Outline Editor. To add an image to a Web Page, carry out the steps described below. In the Library, locate the folder containing the Web Page to which you want to add the image. Double-click on the Web Page, to open it in the Web Page Editor. As before, this is opened in a separate tab in the main UPK Developer window, as can be seen in the next screenshot: Within the Web Page, position the cursor at the place in the text where you want the image to appear. Select menu option Insert|Image. The Insert Image dialog box is displayed, as shown in the next screenshot: In the Link to bar on the leftmost side of the dialog box, select the location of the image file that you want to insert into the Web Page. You can insert an image that you have already imported into your Library (for example, in a Package), an image that is located on your computer (or any attached drive) (option Image on My Computer), or an image from the Internet (option URL). For our sample exercise, we will insert an image from our computer. In the rightmost side of the dialog box, navigate to and select the image file that you want to insert into the Web Page. Click on OK. The image is inserted, as shown in the following screenshot: Save the Web Page, and close the Web Page Editor, if you have finished editing this Web Page. (We have not; we want to adjust the image, as explained below). Of course, this doesn't look too pretty. Thankfully, we can do something about this, because UPK provides some rudimentary features for adjusting images in Web Pages. To adjust the size or position of an image in a Web Page, carry out the following steps: With the Web Page open in the Web Page Editor, right-click on the image that you want to adjust, and then select Image Properties from the context menu. The Image Properties dialog box is displayed, as shown in the next screenshot: In the Alternative Text field, enter a short text description of the image. This will be used as the ToolTip in some web browsers. Under Size, select whether you want to use the original image size or not, and specify a new height and width if you choose not. Under Appearance, select a border width and indicate where the image should be aligned on the Web Page. Your choices are: Top, Middle, Bottom, Left, and Right. The first three of these (the vertical options) control the position of the image relative to the line of text in which it is located. The last two (the horizontal options) determine whether the image is left-aligned or right-aligned within the overall Web Page. Although these are two independent things (vertical and horizontal), you can only select one, so if you want the image to be right-aligned and halfway down the page, you can't do it. Click on OK to confirm your changes. Save and close the Web Page. For our sample exercise, we resized the image, set it to be right-aligned, and added a 1pt border around it (because it looked odd with a white background against the blue of the Web Page, without a border). A better option would be to use an image with a transparent background. In this example we have used an image with a solid background just for the purposes of illustration. These settings are as shown in the previous screenshot. This gives us a final Web Page as shown in the next screenshot. Note that this screenshot is taken from the Player, so that you can see how images are handled in the Player. Note that the text flows around the image. You will see that there is a little more space to the right of the image than there is above it. This is to leave room for a scrollbar. Creating independent Web Pages In the previous section, we looked at how to use a Web Page to add information to the Concept pane of a content object. In this section, we will look at how to use Web Pages to provide information in other areas. Observant readers will have noticed that a Web Page is in fact an independent content object itself. When you created a Web Page to attach to a Concept pane, you edited this Web Page in its own tabbed editor and saved it to its own folder (our Assets folder). Hopefully, you also noticed that in addition to the Create new web page button (), the Concept pane also has a Create link button () that can be used to attach an existing Web Page to the Concept pane. It should, therefore, come as no surprise to learn that Web Pages can be created independently of the Concept pane. In fact, the Concept pane is only one of several uses of Web Pages. To create a Web Page that is independent of a Concepts pane (or anything else), carry out these steps. In the Library, navigate to the folder in which you want to store the Web Page. In our example, we are saving all of the Web Pages for a module in a sub-folder called Assets within the course folder. Select menu option File|New|Web Page. A Web Page Editor tabbed page is opened up on the Developer screen. The content and use of this is exactly as described above in the explanation of how to create a Web Page from within an Outline. Enter the required information into the Web Page, and format it as required. We have already covered most of the available options, above. Once you have made your changes, click on the Save button (). You will be prompted to select the destination folder (which will default to the folder selected in Step 1, although you can change this) and a file name. Refer to the description of the Save As dialog box above for additional help if necessary. Close the Web Page Editor. You have now created a new Web Page. Now let's look at how to use it. Using Web Pages in Topics If you recall our long-running exercise on maintaining your SAP user profile, you will remember that we ask the user to enter their last name and their first name. These terms may be confusing in some countries—especially in countries where the "family name" actually comes before the "given name"—so we want to provide some extra explanation of some common name formats in different countries, and how these translate into first name and last name. We'll provide this information in a Web Page, and then link to this Web Page at the relevant place(s) within our Topic. First, we need to create the Web Page. How to do this is explained in the section Creating independent Web Pages, above. For our exercise, our created Web Page is as follows: There are two ways in which you can link to a Web Page from a Topic. These are explained separately, below. Linking via a hyperlink With a hyperlink, the Web Page is linked from a word or phrase within the Bubble Text of a Frame. (Note that it is only possible to do this for Custom Text because you can't select the Template Text to hyperlink from.) To create a hyperlink to a Web Page from within a Frame in a Topic, carry out the steps described below: Open up the Topic in the Topic Editor. Navigate to the Frame from which you want to provide the hyperlink. In our exercise, we will link from the Explanation Frame describing the Last name field (this is Frame 5B). In the Bubble Properties pane, select the text that you want to form the hyperlink (that is, the text that the user will click on to display the Web Page). Click on the Bubble text link button () in the Bubble Properties pane. The Bubble Text Link Properties dialog box is displayed. Click on the Create link button () to create a link to an existing Web Page (you could also click on the Create new web page button () to create a new Web Page if you have not yet created it). The Insert Hyperlink dialog box is displayed, as shown in the next screenshot: Make sure that the Document in Library option is selected in the Link to: bar. In the Look in: field, navigate to the folder containing the Web Page (the Assets folder, in our example). In the file list, click on the Web Page to which you want to create a hyperlink. Click on the Open button. Back in the Bubble Text Link Properties dialog box, click on OK. This hyperlink will now appear as follows, in the Player: Note that there is no ToolTip for this hyperlink. There was no opportunity to enter one in the steps above, so UPK doesn't know what to use. Version Difference In OnDemand 8.7 the Infoblock name was used as the ToolTip, but this is not the case from OnDemand 9.x onwards.
Read more
  • 0
  • 0
  • 3648
Modal Close icon
Modal Close icon