Thursday, October 15, 2009

Item 37: Optimize judiciously




< BACKCONTINUE >


Item 37: Optimize judiciously


There are three aphorisms concerning optimization that everyone should know. They are perhaps beginning to suffer from overexposure, but in case you aren't yet familiar with them, here they are:





More computing sins are committed in the name of efficiency (without necessarily achieving it) than for any other single reason-including blind stupidity.


--William A. Wulf [Wulf72]





We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil.


--Donald E. Knuth [Knuth74]





We follow two rules in the matter of optimization:


Rule 1. Don't do it.


Rule 2 (for experts only). Don't do it yet-that is, not until you have a perfectly clear and unoptimized solution.


--M. A. Jackson [Jackson75]



All of these aphorisms predate the Java programming language by two decades. They tell a deep truth about optimization: It is easy to do more harm than good, especially if you optimize prematurely. In the process, you may produce software that is neither fast nor correct and cannot easily be fixed.



Don't sacrifice sound architectural principles for performance. Strive to write good programs rather than fast ones. If a good program is not fast enough, its architecture will allow it to be optimized. Good programs embody the principle of information hiding: Where possible, they localize design decisions within individual modules, so individual decisions can be changed without affecting the remainder of the system (Item 12).



This does not mean that you can ignore performance concerns until your program is complete. Implementation problems can be fixed by later optimization, but pervasive architectural flaws that limit performance can be nearly impossible to fix without rewriting the system. Changing a fundamental facet of your design after the fact can result in an ill-structured system that is difficult to maintain and evolve. Therefore you should think about performance during the design process.



Strive to avoid design decisions that limit performance.�

The components of a design that are most difficult to change after the fact are those specifying interactions between modules and with the outside world. Chief among these design components are APIs, wire-level protocols, and persistent data formats. Not only are these design components difficult or impossible to change after the fact, but all of them can place significant limitations on the performance that a system can ever achieve.



Consider the performance consequences of your API design decisions.�

Making a public type mutable may require a lot of needless defensive copying (Item 24). Similarly, using inheritance in a public class where composition would have been appropriate ties the class forever to its superclass, which can place artificial limits on the performance of the subclass (Item 14). As a final example, using an implementation type rather than an interface in an API ties you to a specific implementation, even though faster implementations may be written in the future (Item 34).



The effects of API design on performance are very real. Consider the getSize method in the java.awt.Component class. The decision that this performance-critical method was to return a Dimension instance, coupled with the decision that Dimension instances are mutable, forces any implementation of this method to allocate a new Dimension instance on every invocation. Even though, as of release 1.3, allocating small objects is relatively inexpensive, allocating millions of objects needlessly can do real harm to performance.



In this case, several alternatives existed. Ideally, Dimension should have been immutable (Item 13); alternatively, the getSize method could have been replaced by two methods returning the individual primitive components of a Dimension object. In fact, two such methods were added to the Component API in the 1.2 release for performance reasons. Preexisting client code, however, still uses the getSize method and still suffers the performance consequences of the original API design decisions.



Luckily, it is generally the case that good API design is consistent with good performance. It is a very bad idea to warp an API to achieve good performance. The performance issue that caused you to warp the API may go away in a future release of the platform or other underlying software, but the warped API and the support headaches that it causes will be with you for life.



Once you've carefully designed your program and produced a clear, concise, and well-structured implementation, then it may be time to consider optimization, assuming you're not already satisfied with the performance of the program. Recall that Jackson's two rules of optimization were "Don't do it," and "(for experts only). Don't do it yet." He could have added one more: Measure performance before and after each attempted optimization.



You may be surprised by what you find. Often attempted optimizations have no measurable effect on performance; sometimes they make it worse. The main reason is that it's difficult to guess where your program is spending its time. The part of the program that you think is slow may not be at fault, in which case you'd be wasting your time trying to optimize it. Common wisdom reveals that programs spend 80 percent of their time in 20 percent of their code.



Profiling tools can help you decide where to focus your optimization efforts. Such tools give you run-time information such as roughly how much time each method is consuming and how many times it is invoked. In addition to focusing your tuning efforts, this can alert you to the need for algorithmic changes. If a quadratic (or worse) algorithm lurks inside your program, no amount of tuning will fix the problem. You must replace the algorithm with one that's more efficient. The more code in the system, the more important it is to use a profiler. It's like looking for a needle in a haystack: The bigger the haystack, the more useful it is to have a metal detector. The Java 2 SDK comes with a simple profiler, and several more sophisticated profiling tools are available commercially.



The need to measure the effects of optimization is even greater on the Java platform than on more traditional platforms, as the Java programming language does not have a strong performance model. The relative costs of the various primitive operations are not well defined. The "semantic gap" between what the programmer writes and what the CPU executes is far greater than in traditional compiled languages which makes it very difficult to reliably predict the performance consequences of any optimization. There are plenty of performance myths floating around that turn out to be half-truths or outright lies.



Not only is the performance model ill-defined, but it varies from JVM implementation to JVM implementation and from release to release. If you will be running your program on multiple JVM implementations, it is important that you measure the effects of your optimization on each. Occasionally you may be forced to make trade-offs between performance on different JVM implementations.



To summarize, do not strive to write fast programs-strive to write good ones; speed will follow. Do think about performance issues while you're designing systems and especially while you're designing APIs, wire-level protocols, and persistent data formats. When you've finished building the system, measure its performance. If it's fast enough, you're done. If not, locate the source of the problems with the aid of a profiler, and go to work optimizing the relevant parts of the system. The first step is to examine your choice of algorithms: No amount of low-level optimization can make up for a poor choice of algorithm. Repeat this process as necessary, measuring the performance after every change, until you're satisfied.








< BACKCONTINUE >

Revisiting the Spinner


Revisiting the Spinner


Next, we revisit the spinner listed in
the previous section. That spinner has two serious drawbacks. First, the spinner
component renders itself, so you could not, for example, attach a separate
renderer to the spinner when you migrate your application to cell phones.


Second, the spinner requires a
roundtrip to the server every time a user clicks the increment or decrement
button. Nobody would implement an industrial-strength spinner with those
deficiencies. Now we see how to address them.


While we are at it, we will also add
another feature to the spinner—the ability to attach value change
listeners.


Using an External Renderer


In the preceding example, the
UISpinner class was in charge of its own
rendering. However, most UI classes delegate rendering to a separate class.
Using separate renderers is a good idea: It becomes easy to replace renderers,
to adapt to a different UI toolkit, or simply to achieve different HTML
effects.


In "Encoding
JavaScript to Avoid Server Roundtrips" on page 404 we see how to use an alternative renderer that uses
JavaScript to keep track of the spinner's value on the client.


Using an external renderer requires these steps:
















1.

Define an ID string for your
renderer.


2.

Declare the renderer in a JSF configuration
file.


3.

Modify your tag class to return the renderer's ID from the
getRendererType method.


4.

Implement the renderer
class.


The identifier—in our case, com.corejsf.Spinner—must
be defined in a JSF configuration file, like this:


  <faces-config>
...
<component>
<component-type>com.corejsf.Spinner</component-type>
<component-class>com.corejsf.UISpinner</component-class>
</component>

<render-kit>
<renderer>
<component-family>javax.faces.Input</component-family>
<renderer-type>com.corejsf.Spinner</renderer-type>
<renderer-class>com.corejsf.SpinnerRenderer</renderer-class>
</renderer>
</render-kit>
</faces-config>


The component-family element
serves to overcome a historical problem. The names of the standard HTML tags are
meant to indicate the component type and the renderer type. For example, an
h:selectOneMenu is a UISelectOne component
whose renderer has type javax.faces.Menu. That same renderer can also
be used for the h:selectManyMenu tag. But the scheme did not work so
well. The renderer for h:inputText writes an HTML input text
field. That renderer will not work for h:outputText—you do not want to
use a text field for output.


So, instead of identifying renderers
by individual components, renderers are determined by the renderer type and the
component family.
Table 9-2 shows the component families of all standard component
classes. In our case, we use the component family javax.faces.Input
because UISpinner is a subclass of
UIInput.


















































Table 9-2. Component Families
of Standard Component Classes
Component ClassComponent Family
UICommandjavax.faces.Command
UIDatajavax.faces.Data
UIFormjavax.faces.Form
UIGraphicjavax.faces.Graphic
UIInputjavax.faces.Input
UIMessagejavax.faces.Message
UIMessagesjavax.faces.Messages
UIOutputjavax.faces.Output
UIPaneljavax.faces.Panel
UISelectBooleanjavax.faces.SelectBoolean
UISelectManyjavax.faces.SelectMany
UISelectOnejavax.faces.SelectOne



The getRendererType of your tag class needs to return the renderer ID.


  public class SpinnerTag extends UIComponentTag {
...
public String getComponentType() { return "com.corejsf.Spinner"; }
public String getRendererType() { return "com.corejsf.Spinner"; }
...
}



Note








Component IDs and renderer IDs have
separate name spaces. It is okay to use the same string as a component ID and a
renderer ID.



It is also a good idea to set the
renderer type in the component constructor:



  public class UISpinner extends UIInput {
public UISpinner() {
setConverter(new IntegerConverter()); // to convert the submitted value
setRendererType("com.corejsf.Spinner"); // this component has a renderer
}
}



Then the
renderer type is properly set if a component is used programmatically, without
the use of tags.


The final step is implementing the renderer itself. Renderers
extend the javax.faces.render.Renderer class. That class has seven
methods, four of which are familiar:





  • void encodeBegin(FacesContext context, UIComponent component)




  • void encodeChildren(FacesContext context, UIComponent component)




  • void encodeEnd(FacesContext context, UIComponent component)




  • void decode(FacesContext context, UIComponent component)


The renderer methods
listed above are almost identical to their component counterparts except that
the renderer methods take an additional argument: a reference to the component
being rendered. To implement those methods for the spinner renderer, we move the
component methods to the renderer and apply code changes to compensate for the
fact that the renderer is passed a reference to the component. That is easy to
do.


Here are the remaining renderer
methods:





  • Object getConvertedValue(FacesContext context, UIComponent component,
    Object submittedValue)




  • boolean getRendersChildren()




  • String convertClientId(FacesContext context, String clientId)


The getConvertedValue
method converts a component's submitted value from a string to an object. The
default implementation in the Renderer class returns the value.


The getRendersChildren method
specifies whether a renderer is responsible for rendering its component's
children. If that method returns true, JSF will
call the renderer's encodeChildren method; if it returns
false (the default behavior), the JSF
implementation will not call that method and the children will be encoded
separately.


The convertClientId method converts an ID string (such
as _id1:monthSpinner) so that it can
be used on the client—some clients may place restrictions on IDs, such as
disallowing special characters. However, the default implementation returns the
ID string, unchanged.


If you have a component that renders
itself, it is usually a simple task to move code from the component to the
renderer. Listing
9-10 and Listing
9-11 show the code for the spinner component and
the renderer, respectively.



Listing 9-10.
spinner2/src/java/com/corejsf/UISpinner.java





  1. package com.corejsf;
2.
3. import javax.faces.component.UIInput;
4. import javax.faces.convert.IntegerConverter;
5.
6. public class UISpinner extends UIInput {
7. public UISpinner() {
8. setConverter(new IntegerConverter()); // to convert the submitted value
9. }
10. }



Listing 9-11.
spinner2/src/java/com/corejsf/SpinnerRenderer.java





  1. package com.corejsf;
2.
3. import java.io.IOException;
4. import java.util.Map;
5. import javax.faces.component.UIComponent;
6. import javax.faces.component.EditableValueHolder;
7. import javax.faces.component.UIInput;
8. import javax.faces.context.FacesContext;
9. import javax.faces.context.ResponseWriter;
10. import javax.faces.convert.ConverterException;
11. import javax.faces.render.Renderer;
12.
13. public class SpinnerRenderer extends Renderer {
14. private static final String MORE = ".more";
15. private static final String LESS = ".less";
16.
17. public Object getConvertedValue(FacesContext context, UIComponent component,
18. Object submittedValue) throws ConverterException {
19. return com.corejsf.util.Renderers.getConvertedValue(context, component,
20. submittedValue);
21. }
22.
23. public void encodeBegin(FacesContext context, UIComponent spinner)
24. throws IOException {
25. ResponseWriter writer = context.getResponseWriter();
26. String clientId = spinner.getClientId(context);
27.
28. encodeInputField(spinner, writer, clientId);
29. encodeDecrementButton(spinner, writer, clientId);
30. encodeIncrementButton(spinner, writer, clientId);
31. }
32.
33. public void decode(FacesContext context, UIComponent component) {
34. EditableValueHolder spinner = (EditableValueHolder) component;
35. Map<String, String> requestMap
36. = context.getExternalContext().getRequestParameterMap();
37. String clientId = component.getClientId(context);
38.
39. int increment;
40. if (requestMap.containsKey(clientId + MORE)) increment = 1;
41. else if (requestMap.containsKey(clientId + LESS)) increment = -1;
42. else increment = 0;
43.
44. try {
45. int submittedValue
46. = Integer.parseInt((String) requestMap.get(clientId));
47.
48. int newValue = getIncrementedValue(component, submittedValue,
49. increment);
50. spinner.setSubmittedValue("" + newValue);
51. spinner.setValid(true);
52. }
53. catch(NumberFormatException ex) {
54. // let the converter take care of bad input, but we still have
55. // to set the submitted value, or the converter won't have
56. // any input to deal with
57. spinner.setSubmittedValue((String) requestMap.get(clientId));
58. }
59. }
60.
61. private void encodeInputField(UIComponent spinner, ResponseWriter writer,
62. String clientId) throws IOException {
63. writer.startElement("input", spinner);
64. writer.writeAttribute("name", clientId, "clientId");
65.
66. Object v = ((UIInput) spinner).getValue();
67. if(v != null)
68. writer.writeAttribute("value", v.toString(), "value");
69.
70. Integer size = (Integer) spinner.getAttributes().get("size");
71. if(size != null)
72. writer.writeAttribute("size", size, "size");
73.
74. writer.endElement("input");
75. }
76.
77. private void encodeDecrementButton(UIComponent spinner,
78. ResponseWriter writer, String clientId) throws IOException {
79. writer.startElement("input", spinner);
80. writer.writeAttribute("type", "submit", null);
81. writer.writeAttribute("name", clientId + LESS, null);
82. writer.writeAttribute("value", "<", "value");
83. writer.endElement("input");
84. }
85.
86. private void encodeIncrementButton(UIComponent spinner,
87. ResponseWriter writer, String clientId) throws IOException {
88. writer.startElement("input", spinner);
89. writer.writeAttribute("type", "submit", null);
90. writer.writeAttribute("name", clientId + MORE, null);
91. writer.writeAttribute("value", ">", "value");
92. writer.endElement("input");
93. }
94.
95. private int getIncrementedValue(UIComponent spinner, int submittedValue,
96. int increment) {
97. Integer minimum = (Integer) spinner.getAttributes().get("minimum");
98. Integer maximum = (Integer) spinner.getAttributes().get("maximum");
99. int newValue = submittedValue + increment;
100.
101. if ((minimum == null || newValue >= minimum.intValue()) &&
102. (maximum == null || newValue <= maximum.intValue()))
103. return newValue;
104. else
105. return submittedValue;
106. }
107. }



Calling Converters from External
Renderers


If you compare Listing 9-10 and Listing 9-11 with Listing
9-1, you will see that we moved most of the code from
the original component class to a new renderer class.


However, there is a hitch. As you can
see from Listing
9-10, the spinner handles conversions
simply by invoking setConverter() in its
constructor. Because the spinner is an input component, its
superclass—UIInput—uses the specified
converter during the Process Validations phase of the life cycle.


But when the spinner delegates to a renderer, it is
the renderer's responsibility to convert the spinner's value by overriding
Renderer.getConvertedValue(). So we must replicate the conversion code
from UIInput in a custom renderer. We
placed that code—which is required in all renderers that use a converter—in the
static getConvertedValue method of the class
com.corejsf.util.Renderers (see Listing 9-12 on page 398).




Note








The Renderers.getConvertedValue method shown in Listing 9-12 is a
necessary evil because UIInput does not make
its conversion code publicly available. That code resides in the protected
UIInput.getConvertedValue method, which looks like
this in the JSF 1.2 Reference Implementation:



// This code is from the javax.faces.component.UIInput class:
public void getConvertedValue(FacesContext context, Object newSubmittedValue)
throws ConverterException {
Object newValue = newSubmittedValue;
if (renderer != null) {
newValue = renderer.getConvertedValue(context, this, newSubmittedValue);
} else if (newSubmittedValue instanceof String) {
Converter converter = getConverterWithType(context); // a private method
if (converter != null)
newValue = converter.getAsObject(
context, this, (String) newSubmittedValue);
}
return newValue;
}



The private getConverterWithType method looks up the
appropriate converter for the component value.


Because UIInput's
conversion code is buried in protected and private methods, it is not available
for a renderer to reuse. Custom components that use converters must duplicate
the code—see, for example, the implementation of
com.sun.faces.renderkit.html_basic.HtmlBasicInputRenderer in the reference implementation. Our
com.corejsf.util.Renderers class provides the
code for use in your own classes.



Supporting Value Change
Listeners


If your custom component is an input
component, you can fire value change events to interested listeners. For
example, in a calendar application, you may want to update another component
whenever a month spinner value changes.


Fortunately, it is easy to support value
change listeners. The UIInput class
automatically generates value change events whenever the input value has
changed. Recall that there are two ways of attaching a value change listener.
You can add one or more listeners with f:valueChangeListener, like
this:


  <corejsf:spinner ...>
<f:valueChangeListener type="com.corejsf.SpinnerListener"/>
...
</corejsf:spinner>


Or you can use a valueChangeListener attribute:


  <corejsf:spinner value="#{cardExpirationDate.month}"
id="monthSpinner" minimum="1" maximum="12" size="3"
valueChangeListener="#{cardExpirationDate.changeListener}"/>


The first way doesn't require any
effort on the part of the component implementor. The second way merely requires
that your tag handler supports the valueChangeListener attribute. The attribute value is a method expression
that requires special handling—the topic of the next section, "Supporting Method
Expressions."


In the sample program, we
demonstrate the value change listener by keeping a count of all value changes
that we display on the form (see Figure 9-7).





Figure 9-7. Counting the value changes






   public class CreditCardExpiration {
private int changes = 0;
// to demonstrate the value change listener
public void changeListener(ValueChangeEvent e) {
changes++;
}
}


Supporting Method Expressions


Four commonly used attributes require
method expressions (see Table 9-3). You declare them in the
TLD file with deferred-method elements, such as the following:


  <attribute>
<name>valueChangeListener</name>
<deferred-method>
<method-signature>
void valueChange(javax.faces.event.ValueChangeEvent)
</method-signature>
</deferred-method>
</attribute>


In the tag handler class, you provide setters for
MethodExpression objects.


  public class SpinnerTag extends UIComponentELTag {
...
private MethodExpression valueChangeListener = null;

public void setValueChangeListener(MethodExpression newValue) {
valueChangeListener = newValue;
}
...
}































Table 9-3. Processing Method Expressions
Attribute Namemethod-signature Element in TLDCode in setProperties Method

valueChangeListener


void valueChange(javax.faces.
event.ValueChangeEvent)


((EditableValueHolder) component)
.addValueChangeListener(new
MethodExpressionValueChangeListener(expr));


validator


void validate(javax.faces.
context.FacesContext,
javax.faces.component.
UIComponent, java.lang.Object)


((EditableValueHolder) component)
.addValidator(new
MethodExpressionValidator(expr));


actionListener


void actionListener(javax.
faces.event.ActionEvent)


((ActionSource) component)
.addActionListener(new
MethodExpressionActionListener(expr));


action


java.lang.Object action()


((ActionSource2) component).
addAction(expr);



In the setProperties method of
the tag handler, you convert the MethodExpression object to an appropriate listener object and add it to
the component:



  public void setProperties(UIComponent component) {
super.setProperties(component);
...
if (valueChangeListener != null)
((EditableValueHolder) component).addValueChangeListener(
new MethodExpressionValueChangeListener(valueChangeListener));
}



Table
9-3 shows how to handle the other method
attributes.




Note








The action attribute value can
be either a method expression or a constant. In the latter case, a method is
created that always returns the constant
value.



The Sample Application


Figure
9-8 shows the directory structure of the sample
application. As in the first example, we rely on the core JSF
Renderers convenience class that contains
the code for invoking the converter.





Figure 9-8. Directory structure of
the revisited spinner example




(The Renderers class also
contains a getSelectedItems method that we need later in this
chapter—ignore it for now.) Listing 9-13 contains the revised
SpinnerTag class, and Listing 9-14 shows the
faces-config.xml file.



Listing 9-12.
spinner2/src/java/com/corejsf/util/Renderers.java





  1. package com.corejsf.util;
2.
3. import java.util.ArrayList;
4. import java.util.Arrays;
5. import java.util.Collection;
6. import java.util.List;
7. import java.util.Map;
8.
9. import javax.el.ValueExpression;
10. import javax.faces.application.Application;
11. import javax.faces.component.UIComponent;
12. import javax.faces.component.UIForm;
13. import javax.faces.component.UISelectItem;
14. import javax.faces.component.UISelectItems;
15. import javax.faces.component.ValueHolder;
16. import javax.faces.context.FacesContext;
17. import javax.faces.convert.Converter;
18. import javax.faces.convert.ConverterException;
19. import javax.faces.model.SelectItem;
20.
21. public class Renderers {
22. public static Object getConvertedValue(FacesContext context,
23. UIComponent component, Object submittedValue)
24. throws ConverterException {
25. if (submittedValue instanceof String) {
26. Converter converter = getConverter(context, component);
27. if (converter != null) {
28. return converter.getAsObject(context, component,
29. (String) submittedValue);
30. }
31. }
32. return submittedValue;
33. }
34.
35. public static Converter getConverter(FacesContext context,
36. UIComponent component) {
37. if (!(component instanceof ValueHolder)) return null;
38. ValueHolder holder = (ValueHolder) component;
39.
40. Converter converter = holder.getConverter();
41. if (converter != null)
42. return converter;
43.
44. ValueExpression expr = component.getValueExpression("value");
45. if (expr == null) return null;
46.
47. Class targetType = expr.getType(context.getELContext());
48. if (targetType == null) return null;
49. // Version 1.0 of the reference implementation will not apply a converter
50. // if the target type is String or Object, but that is a bug.
51.
52. Application app = context.getApplication();
53. return app.createConverter(targetType);
54. }
55.
56. public static String getFormId(FacesContext context, UIComponent component) {
57. UIComponent parent = component;
58. while (!(parent instanceof UIForm))
59. parent = parent.getParent();
60. return parent.getClientId(context);
61. }
62.
63. @SuppressWarnings("unchecked")
64. public static List<SelectItem> getSelectItems(UIComponent component) {
65. ArrayList<SelectItem> list = new ArrayList<SelectItem>();
66. for (UIComponent child : component.getChildren()) {
67. if (child instanceof UISelectItem) {
68. Object value = ((UISelectItem) child).getValue();
69. if (value == null) {
70. UISelectItem item = (UISelectItem) child;
71. list.add(new SelectItem(item.getItemValue(),
72. item.getItemLabel(),
73. item.getItemDescription(),
74. item.isItemDisabled()));
75. } else if (value instanceof SelectItem) {
76. list.add((SelectItem) value);
77. }
78. } else if (child instanceof UISelectItems) {
79. Object value = ((UISelectItems) child).getValue();
80. if (value instanceof SelectItem)
81. list.add((SelectItem) value);
82. else if (value instanceof SelectItem[])
83. list.addAll(Arrays.asList((SelectItem[]) value));
84. else if (value instanceof Collection)
85. list.addAll((Collection<SelectItem>) value); // unavoidable
86. // warning
87. else if (value instanceof Map) {
88. for (Map.Entry<?, ?> entry : ((Map<?, ?>) value).entrySet())
89. list.add(new SelectItem(entry.getKey(),
90. "" + entry.getValue()));
91. }
92. }
93. }
94. return list;
95. }
96. }




Listing 9-13.
spinner2/src/java/com/corejsf/SpinnerTag.java





  1. package com.corejsf;
2.
3. import javax.el.MethodExpression;
4. import javax.el.ValueExpression;
5. import javax.faces.component.EditableValueHolder;
6. import javax.faces.component.UIComponent;
7. import javax.faces.event.MethodExpressionValueChangeListener;
8. import javax.faces.webapp.UIComponentELTag;
9.
10. public class SpinnerTag extends UIComponentELTag {
11. private ValueExpression minimum = null;
12. private ValueExpression maximum = null;
13. private ValueExpression size = null;
14. private ValueExpression value = null;
15. private MethodExpression valueChangeListener = null;
16.
17. public String getRendererType() { return "com.corejsf.Spinner"; }
18. public String getComponentType() { return "com.corejsf.Spinner"; }
19.
20. public void setMinimum(ValueExpression newValue) { minimum = newValue; }
21. public void setMaximum(ValueExpression newValue) { maximum = newValue; }
22. public void setSize(ValueExpression newValue) { size = newValue; }
23. public void setValue(ValueExpression newValue) { value = newValue; }
24. public void setValueChangeListener(MethodExpression newValue) {
25. valueChangeListener = newValue;
26. }
27.
28. public void setProperties(UIComponent component) {
29. // always call the superclass method
30. super.setProperties(component);
31.
32. component.setValueExpression("size", size);
33. component.setValueExpression("minimum", minimum);
34. component.setValueExpression("maximum", maximum);
35. component.setValueExpression("value", value);
36. if (valueChangeListener != null)
37. ((EditableValueHolder) component).addValueChangeListener(
38. new MethodExpressionValueChangeListener(valueChangeListener));
39. }
40.
41. public void release() {
42. // always call the superclass method
43. super.release();
44.
45. minimum = null;
46. maximum = null;
47. size = null;
48. value = null;
49. valueChangeListener = null;
50. }
51. }




Listing 9-14.
spinner2/web/WEB-INF/faces-config.xml





  1. <?xml version="1.0"?>
2.
3. <faces-config xmlns="http://java.sun.com/xml/ns/javaee"
4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5. xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
6. http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd"
7. version="1.2">
8.
9. <navigation-rule>
10. <from-view-id>/index.jsp</from-view-id>
11. <navigation-case>
12. <from-outcome>next</from-outcome>
13. <to-view-id>/next.jsp</to-view-id>
14. </navigation-case>
15. </navigation-rule>
16.
17. <navigation-rule>
18. <from-view-id>/next.jsp</from-view-id>
19. <navigation-case>
20. <from-outcome>again</from-outcome>
21. <to-view-id>/index.jsp</to-view-id>
22. </navigation-case>
23. </navigation-rule>
24.
25. <managed-bean>
26. <managed-bean-name>cardExpirationDate</managed-bean-name>
27. <managed-bean-class>com.corejsf.CreditCardExpiration</managed-bean-class>
28. <managed-bean-scope>session</managed-bean-scope>
29. </managed-bean>
30.
31. <component>
32. <component-type>com.corejsf.Spinner</component-type>
33. <component-class>com.corejsf.UISpinner</component-class>
34. </component>
35.
36. <render-kit>
37. <renderer>
38. <component-family>javax.faces.Input</component-family>
39. <renderer-type>com.corejsf.Spinner</renderer-type>
40. <renderer-class>com.corejsf.SpinnerRenderer</renderer-class>
41. </renderer>
42. </render-kit>
43.
44. <application>
45. <resource-bundle>
46. <base-name>com.corejsf.messages</base-name>
47. <var>msgs</var>
48. </resource-bundle>
49. </application>
50. </faces-config>




javax.faces.component.EditableValueHolder










  • void addValueChangeListener(ValueChangeListener
    listener)
    JSF 1.2


    Adds a value change listener to this component.



  • void addValidator(Validator val) JSF 1.2


    Adds a validator to this component.




javax.faces.component.ActionSource










  • void addActionListener(ActionListener listener) JSF 1.2


    Adds an action listener to this component.




javax.faces.component.ActionSource2 JSF 1.2










  • void addAction(MethodExpression m)


    Adds an action to this component. The method has return type
    String and no
    parameters.




javax.faces.event.MethodExpressionValueChangeListener
JSF 1.2










  • MethodExpressionValueChangeListener(MethodExpression
    m)


    Constructs a value change listener
    from a method expression. The method must return void and is passed a
    ValueChangeEvent.




javax.faces.validator.MethodExpressionValidator
JSF 1.2










  • MethodExpressionValidator(MethodExpression m)


    Constructs a validator from a method
    expression. The method must return void and is passed a FacesContext, a
    UIComponent, and an
    Object.




javax.faces.event.MethodExpressionActionListener
JSF 1.2










  • MethodExpressionActionListener(MethodExpression m)


    Constructs an action listener from a
    method expression. The method must return void and is passed an
    ActionEvent.




javax.faces.event.ValueChangeEvent










  • Object getOldValue()


    Returns the component's old value.



  • Object getNewValue()


    Returns the component's new
    value.




javax.faces.component.ValueHolder










  • Converter getConverter()


    Returns the converter associated with a component. The
    ValueHolder inter-face is implemented by input and output
    components.




javax.faces.component.UIComponent










  • ValueExpression getValueExpression(String name) JSF 1.2


    Returns the value expression associated with the given name.




javax.faces.context.FacesContext










  • ELContext getELContext() JSF
    1.2


    Returns the expression language
    context.




javax.el.ValueExpression JSF 1.2










  • Class getType(ELContext context)


    Returns the type of this value
    expression.




javax.faces.application.Application










  • Converter createConverter(Class targetClass)


    Creates a converter, given its target
    class. JSF implementations maintain a map of valid converter types, which are
    typically specified in a faces configuration file. If targetClass is a key in that map, this method creates an instance of the
    associated converter (specified as the value for the target-Class key)
    and returns it.


    If targetClass is not in the
    map, this method searches the map for a key that corresponds to
    targetClass's interfaces and superclasses, in
    that order, until it finds a matching class. Once a matching class is found,
    this method creates an associated converter and returns it. If no converter is
    found for the targetClass, its
    interfaces, or its superclasses, this method returns
    null.

8.4 Determining a Formatting Locale








 

 










8.4 Determining a Formatting Locale



So far in this chapter, all of the code examples have used <fmt:setLocale> to specify the locale used by the <fmt:formatNumber>, <fmt:parseNumber>, <fmt:formatDate>, and <fmt:parseDate> actions. But in practice, it's usually not necessary to use <fmt:setLocale> to establish a formatting locale because the formatting actions perform a rather elaborate search for a locale. This section discusses that search.



Before we discuss the search for a formatting locale, you must understand the concept of a localization context. You can read about localization contexts in "Localization Context Lookup" on page 268, but in a nutshell, a localization context is a simple JavaBeans component (bean) that maintains a resource bundle and a locale. For our purposes in this chapter, the resource bundle is immaterial, but the locale stored in a localization context is often used by formatting actions.



The search that formatting actions perform to locate a formatting locale proceeds as follows:





  1. An Enclosing <fmt:bundle> Action

    All <fmt:bundle> actions establish a localization context, meaning they store a resource bundle and a locale in a localization context. If a formatting action is nested in a <fmt:bundle> action, it uses the locale stored in the localization context established by its enclosing <fmt:bundle> action.



  2. The FMT_LOCALIZATION_CONTEXT Configuration Setting

    If a formatting action is not nested in a <fmt:bundle> action, it checks to see if the FMT_LOCALIZATION_CONTEXT configuration setting has been set; if so, the formatting action uses the locale stored in that configuration setting.



  3. Formatting Locale Lookup

    If a formatting action is not nested in a <fmt:bundle> action and the FMT_LOCALIZATION_CONTEXT configuration setting has not been set, the formatting action performs a formatting locale lookup. That lookup is discussed in "Formatting Locale Lookup" on page 354.





Let's discuss each of the preceding steps in more detail.



1 An Enclosing <fmt:bundle> Action



Formatting actions that are nested in a <fmt:bundle> action use the locale stored in that <fmt:bundle> action's localization context; for example:





<%-- The following <fmt:bundle> action establishes a localization

context that is only used in the body of the <fmt:bundle>

action --%>

<fmt:bundle basename='messages'>

<%-- The i18n and formatting actions nested in the enclosing

<fmt:bundle> action use the localization context

established by the enclosing <fmt:bundle> action --%>

<fmt:message key='formatting.example.number'/>

...



<fmt:formatNumber value='234682.155'/>

...

</fmt:bundle>



In the preceding code fragment, the <fmt:bundle> action tries to locate a resource bundle whose base name is messages; if it finds that resource bundle, it creates a localization context and stores the resource bundle and the locale that was used to locate that resource bundle in its localization context.[17] All of the <fmt:message> actions and all of the formatting actions nested in that <fmt:bundle> action use the same localization context; for example, in the preceding code fragment, the <fmt:message> action uses the resource bundle stored in the <fmt:bundle> action's localization context, and the <fmt:formatNumber> action uses the locale stored in the <fmt:bundle> action's localization context.



[17] See "Resource Bundle Lookup" on page 274 for more information about how <fmt:bundle> establishes a localization context.





2 The FMT_LOCALIZATION_CONTEXT Configuration Setting



If a formatting action is not nested in a <fmt:bundle> action and the FMT_LOCALIZATION_CONTEXT configuration setting has been set, that formatting action uses the locale stored in the FMT_LOCALIZATION_CONTEXT configuration setting's localization context; for example:





<%-- This <fmt:setBundle> action establishes a localization context

and stores it in the FMT_LOCALIZATION_CONTEXT configuration

setting --%>

<fmt:setBundle basename='messages'/>



<%-- Because the following <fmt:formatNumber> action is not nested

in a <fmt:bundle> action, it gets its locale from the

localization context established by the preceding

<fmt:setBundle> action --%>

<fmt:formatNumber value='234682.155'/>



In the preceding code fragment, the FMT_LOCALIZATION_CONTEXT configuration setting is set by the <fmt:setBundle> action.[18] The locale stored in the FMT_LOCALIZATION_CONTEXT configuration setting is used by the <fmt:formatNumber> action.



[18] There are other ways to set the FMT_LOCALIZATION_CONTEXT configuration setting; see "Localization Context Lookup" on page 268 for more information.



If the localization context stored in the FMT_LOCALIZATION_CONTEXT configuration setting does not have a locale, formatting actions that do not reside in the body of a <fmt:bundle> action perform a formatting locale lookup, which is described in the next step.





3 Formatting Locale Lookup



If a formatting action is not nested in a <fmt:bundle> action and the FMT_LOCALIZATION_CONTEXT configuration setting has not been set, that formatting action performs a formatting locale lookup; for example:





<%-- If the FMT_LOCALIZATION_CONTEXT configuration setting has not

been set, the following <fmt:formatNumber> action performs

a formatting locale lookup to find a locale to use to

format its value --%>

<fmt:formatNumber value='234682.155'/>



In the preceding code fragment, the <fmt:formatNumber> action is not nested in a <fmt:bundle> action. If the FMT_LOCALIZATION_CONTEXT configuration setting has not been set, that <fmt:formatNumber> action performs a formatting locale lookup, which is discussed in the next section.





Formatting Locale Lookup



Formatting actions that are not nested in a <fmt:bundle> action perform a formatting locale lookup if the FMT_LOCALIZATION_CONTEXT configuration setting has not been set or if that configuration setting does not contain a locale.



The formatting locale lookup tries to find an appropriate locale among a set of available locales. For <fmt:formatNumber> and <fmt:parseNumber>, the available locales are determined by a call to the getAvailableLocales method from java.text.NumberFormat. For <fmt:formatDate> and <fmt:parseDate>, the available locales are determined by a call to the getAvailableLocales method from java.text.DateFormat.



The formatting locale lookup proceeds as follows:











  1. Find a Matching Locale with the User's Preferred Locales



    First, the formatting actions compare each of the user's preferred locales against each of the available locales; when a match is found, the algorithm terminates and that locale is used by the formatting action.



    There are two ways that you can specify your preferred locales: with your browser's language preferences or by setting the FMT_LOCALE configuration setting; the latter takes precedence over the former. You can set the FMT_LOCALE configuration setting in a number of ways; one way is with the <fmt:setLocale> action, which is how most of the code examples in this chapter establish a formatting locale.





  2. Find a Matching Locale with the Fallback Locale



    If a formatting action cannot find a matching locale with a user's preferred locales, it will compare the fallback locale with each of the available locales.



    The fallback locale is specified with the FMT_FALLBACK_LOCALE configuration setting. There is no JSTL action that sets the FMT_FALLBACK_LOCALE configuration setting, so you must set that configuration setting in a deployment descriptor or a business component.





A preferred locale (or the fallback locale) matches an available locale if:





  • They match exactly; that is, if the language, country, and variants all match, OR:



  • The language and country match, OR:



  • The language matches and the available locale does not specify a country.


















     

     


    Section 7.4. Processing Forms










    7.4. Processing Forms





    It's easy to process forms
    with PHP, as the form parameters are available in the $_GET and $_POST arrays. There are many tricks and techniques for working with forms, though, which are described in this section.



    7.4.1. Methods


    As we already discussed, there are two HTTP methods that a client can use to pass form data to the server: GET and POST. The method that a particular form uses is specified with the method attribute to the form tag. In theory methods are case-insensitive in the HTML, but in practice some broken browsers require the method name to be in all uppercase.


    A GET request encodes the form parameters in the URL in what is called a query string:



    /path/to/chunkify.php?word=despicable&length=3



    A POST request passes the form parameters in the body of the HTTP request, leaving the URL untouched.


    The most visible difference between GET and POST is the URL line. Because all of a form's parameters

    are encoded in the URL with a GET request, users can bookmark GET queries. They cannot do this with POST requests

    , however.


    The biggest difference between GET and POST requests, however, is far more subtle. The HTTP specification says that GET requests

    are idempotentthat is, one GET request for a particular URL, including form parameters, is the same as two or more requests for that URL. Thus, web browsers can cache the response pages for GET requests, because the response page doesn't change regardless of how many times the page is loaded. Because of idempotence


    , GET requests should be used only for queries such as splitting a word into smaller chunks or multiplying numbers, where the response page is never going to change.


    POST requests are not idempotent. This means that they cannot be cached, and the server is recontacted every time the page is displayed. You've probably seen your web browser prompt you with "Repost form data?" before displaying or reloading certain pages. This makes POST requests the appropriate choice for queries whose response pages may change over timefor example, displaying the contents of a shopping cart or the current messages in a bulletin board.


    That said, idempotence is often ignored in the real world. Browser caches are generally so poorly implemented, and the Reload button is so easy to hit, that programmers tend to use GET and POST simply based on whether they want the query parameters shown in the URL or not. What you need to remember is that GET requests should not be used for any actions that cause a change in the server, such as placing an order or updating a database.


    The type of method that was used to request a PHP page is available through $_SERVER['REQUEST_METHOD']. For example:



    if ($_SERVER['REQUEST_METHOD'] == 'GET') {
    // handle a GET request
    } else {
    die("You may only GET this page.");
    }





    7.4.2. Parameters



    Use the $_POST, $_GET, and $_FILES arrays to access form parameters from your PHP code. The keys are the parameter names, and the values are the values of those parameters. Because periods are legal in HTML field names but not in PHP variable names, periods in field names are converted to underscores (_) in the array.


    Example 7-1 shows an HTML form that chunkifies a string supplied by the user. The form contains two fields: one for the string (parameter name "word") and one for the size of chunks to produce (parameter name "number").


    Example 7-1. The chunkify form (chunkify.html)




    <html>
    <head><title>Chunkify Form</title></head>
    <body>
    <form action="chunkify.php" method="POST">
    Enter a word: <input type="text" name="word" /><br />
    How long should the chunks be?
    <input type="text" name="number" /><br />
    <input type="submit" value="Chunkify!">
    </form>
    </body>
    </html>



    Example 7-2 lists the PHP script, chunkify.php, to which the form in Example 7-1 submits. The script copies the parameter values into variables and uses them. Although the register_globals option in php.ini would automatically create variables from the parameter values, we don't use it because it complicates writing secure PHP programs.


    Example 7-2. The chunkify script (chunkify.php)




    <html>
    <head><title>Chunked Word</title></head>
    <body>

    <?php
    $word = $_POST['word'];
    $number = $_POST['number'];

    $chunks = ceil(strlen($word)/$number);

    echo "The $number-letter chunks of '$word' are:<br />\n";

    for ($i=0; $i < $chunks; $i++) {
    $chunk = substr($word, $i*$number, $number);
    printf("%d: %s<br />\n", $i+1, $chunk);
    }
    ?>

    </body>
    </html>



    Figure 7-1 shows the both the chunkify form and the resulting output.




    7.4.3. Automatic Quoting of Parameters






    PHP ships with the magic_quotes_gpc option enabled in php.ini. This option instructs PHP to automatically call addslashes( ) on all cookie data and GET and POST parameters. This makes it easy to use form parameters in database queries, as we'll see in Chapter 8, but can cause trouble with form parameters not used in database queries, because all single quotes, double quotes, backslashes, and NUL-bytes are escaped with backslash characters.



    Figure 7-1. The chunkify form and its output



    For instance, if you enter the word "O'Reilly" in the form in Figure 7-1 and hit the Chunkify button, you'll see that the word that's actually chunked is "O\'Reilly." That's magic_quotes_gpc at work.


    To work with the strings as typed by the user, you can either disable magic_quotes_gpc in php.ini or use the stripslashes( ) function on the values in $_GET, $_POST, and $_COOKIES. The correct way to work with a string is as follows:



    $value = ini_get('magic_quotes_gpc')
    ? stripslashes($_GET['word'])
    : $_GET['word'];



    If you plan to work with lots of string values, it's wise to define a function to handle this for you:



    function raw_param ($name) {
    return ini_get('magic_quotes_gpc')
    ? stripslashes($_GET[$name])
    : $_GET[$name];
    }



    You call the function like this:



    $value = raw_param('word');



    For the remaining examples in this chapter, we'll assume that you have magic_quotes_gpc disabled in php.ini. If you don't, you'll need to change the examples to call stripslashes( ) on all the parameters.





    7.4.4. Self-Processing Pages





    One PHP page can be used to both generate a form and process it. If the page shown in Example 7-3 is requested with the GET method, it prints a form that accepts a Fahrenheit temperature. If called with the POST method, however, the page calculates and displays the corresponding Celsius temperature.


    Example 7-3. A self-processing temperature-conversion page (temp.php)




    <html>
    <head><title>Temperature Conversion</title></head>
    <body>

    <?php
    if ($_SERVER['REQUEST_METHOD'] == 'GET') {
    ?>

    <form action="<?php echo $_SERVER['PHP_SELF'] ?>" method="POST">
    Fahrenheit temperature:
    <input type="text" name="fahrenheit" /> <br />
    <input type="submit" name="Convert to Celsius!" />
    </form>

    <?php
    } elseif ($_SERVER['REQUEST_METHOD'] == 'POST') {
    $fahr = $_POST['fahrenheit'];
    $celsius = ($fahr - 32) * 5/9;
    printf("%.2fF is %.2fC", $fahr, $celsius);
    } else {
    die("This script only works with GET and POST requests.");
    }
    ?>

    </body>
    </html>



    Figure 7-2 shows the temperature-conversion page and the resulting output.



    Figure 7-2. The temperature-conversion page and its output



    Another way for a script to decide whether to display a form or process it is to see whether or not one of the parameters has been supplied. This lets you write a self-processing page that uses the GET method to submit values. Example 7-4 shows a new version of the temperature-conversion page that submits parameters using a GET request. This page uses the presence or absence of parameters to determine what to do.


    Example 7-4. Temperature conversion using the GET method




    <html>
    <head><title>Temperature Conversion</title></head>
    <body>

    <?php
    $fahr = $_GET['fahrenheit'];
    if (is_null($fahr)) {
    ?>

    <form action="<?php echo $_SERVER['PHP_SELF'] ?>" method="GET">
    Fahrenheit temperature:
    <input type="text" name="fahrenheit" /> <br />
    <input type="submit" name="Convert to Celsius!" />
    </form>

    <?php
    } else {
    $celsius = ($fahr - 32) * 5/9;
    printf("%.2fF is %.2fC", $fahr, $celsius);
    }
    ?>

    </body>
    </html>



    In Example 7-4, we copy the form parameter value into $fahr. If we weren't given that parameter, $fahr contains NULL, so we can use is_null( ) to test whether we should display the form or process the form data.




    7.4.5. Sticky Forms




    Many web sites use a technique known as sticky
    forms
    , in which the results of a query are accompanied by a search form whose default values are those of the previous query. For instance, if you search Google (http://www.google.com) for "Programming PHP," the top of the results page contains another search box, which already contains "Programming PHP." To refine your search to "Programming PHP from O'Reilly," you can simply add the extra keywords.


    This sticky behavior is easy to implement. Example 7-5 shows our temperature-conversion script from Example 7-4, with the form made sticky. The basic technique is to use the submitted form value as the default value when creating the HTML field.


    Example 7-5. Temperature conversion with a sticky form




    <html>
    <head><title>Temperature Conversion</title></head>
    <body>

    <?php
    $fahr = $_GET['fahrenheit'];
    ?>

    <form action="<?php echo $_SERVER['PHP_SELF'] ?>" method="GET">
    Fahrenheit temperature:
    <input type="text" name="fahrenheit" value="<?php echo $fahr ?>" />
    <br />
    <input type="submit" name="Convert to Celsius!" />
    </form>

    <?php
    if (! is_null($fahr)) {
    $celsius = ($fahr - 32) * 5/9;
    printf("%.2fF is %.2fC", $fahr, $celsius);
    }
    ?>

    </body>
    </html>





    7.4.6. Multivalued Parameters






    HTML selection lists, created with the select tag, can allow multiple selections. To ensure that PHP recognizes the multiple values that the browser passes to a form-processing script, you need to make the name of the field in the HTML form end with []. For example:



    <select name="languages[]">
    <input name="c">C</input>
    <input name="c++">C++</input>
    <input name="php">PHP</input>
    <input name="perl">Perl</input>
    </select>



    Now, when the user submits the form, $_GET['languages'] contains an array instead of a simple string. This array contains the values that were selected by the user.


    Example 7-6 illustrates multiple selection. The form provides the user with a set of personality attributes. When the user submits the form, he gets a (not very interesting) description of his personality.


    Example 7-6. Multiple selection values with a select box




    <html>
    <head><title>Personality</title></head>
    <body>

    <form action="<?php echo $_SERVER['PHP_SELF'] ?>" method="GET">
    Select your personality attributes:<br />
    <select name="attributes[]" multiple>
    <option value="perky">Perky</option>
    <option value="morose">Morose</option>
    <option value="thinking">Thinking</option>
    <option value="feeling">Feeling</option>
    <option value="thrifty">Spend-thrift</option>
    <option value="prodigal">Shopper</option>
    </select>
    <br>
    <input type="submit" name="s" value="Record my personality!" />
    </form>

    <?php
    if (array_key_exists('s', $_GET)) {
    $description = join (" ", $_GET['attributes']);
    echo "You have a $description personality.";
    }
    ?>

    </body>
    </html>



    In Example 7-6, the submit button has a name, "s". We check for the presence of this parameter value to see whether we have to produce a personality description. Figure 7-3 shows the multiple selection page and the resulting output.



    Figure 7-3. Multiple selection and its output



    The same technique applies for any form field where multiple values can be returned. Example 7-7 shows a revised version of our personality form that is rewritten to use checkboxes instead of a select box. Notice that only the HTML has changedthe code to process the form doesn't need to know whether the multiple values came from checkboxes or a select box.


    Example 7-7. Multiple selection values in checkboxes




    <html>
    <head><title>Personality</title></head>
    <body>

    <form action="<?php $_SERVER['PHP_SELF'] ?>" method="GET">
    Select your personality attributes:<br />
    Perky <input type="checkbox" name="attributes[]" value="perky" /><br />
    Morose <input type="checkbox" name="attributes[]" value="morose" /><br />
    Thinking <input type="checkbox" name="attributes[]" value="feeling" /><br />
    Feeling <input type="checkbox" name="attributes[]" value="feeling" /><br />
    Spend-thrift <input type="checkbox" name="attributes[]" value="thrifty" /><br />
    Shopper <input type="checkbox" name="attributes[]" value="thrifty" /><br />
    <br />
    <input type="submit" name="s" value="Record my personality!" />
    </form>

    <?php
    if (array_key_exists('s', $_GET)) {
    $description = join (" ", $_GET['attributes']);
    echo "You have a $description personality.";
    }
    ?>

    </body>
    </html>





    7.4.7. Sticky Multivalued Parameters





    So now you're wondering, can I make multiple selection form elements sticky? You can, but it isn't easy. You'll need to check to see whether each possible value in the form was one of the submitted values. For example:



    Perky: <input type="checkbox" name="attributes[]" value="perky"
    <?= if (is_array($_GET['attributes']) and
    in_array('perky', $_GET['attributes'])) {
    "checked";
    }
    ?> /><br />



    You could use this technique for each checkbox, but that's repetitive and error-prone. At this point, it's easier to write a function to generate the HTML for the possible values and work from a copy of the submitted parameters. Example 7-8 shows a new version of the multiple selection checkboxes, with the form made sticky. Although this form looks just like the one in Example 7-7, behind the scenes there are substantial changes to the way the form is generated.


    Example 7-8. Sticky multivalued checkboxes




    <html>
    <head><title>Personality</title></head>
    <body>

    <?php
    // fetch form values, if any
    $attrs = $_GET['attributes'];
    if (! is_array($attrs)) { $attrs = array( ); }

    // create HTML for identically named checkboxes

    function make_checkboxes ($name, $query, $options) {
    foreach ($options as $value => $label) {
    printf('%s <input type="checkbox" name="%s[]" value="%s" ',
    $label, $name, $value);
    if (in_array($value, $query)) { echo "checked "; }
    echo "/><br />\n";
    }
    }

    // the list of values and labels for the checkboxes
    $personality_attributes = array(
    'perky' => 'Perky',
    'morose' => 'Morose',
    'thinking' => 'Thinking',
    'feeling' => 'Feeling',
    'thrifty' => 'Spend-thrift',
    'prodigal' => 'Shopper'
    );
    ?>

    <form action="<?php $_SERVER['PHP_SELF'] ?>" method="GET">
    Select your personality attributes:<br />
    <?php make_checkboxes('attributes', $attrs, $personality_attributes); ?>
    <br />
    <input type="submit" name="s" value="Record my personality!" />
    </form>

    <?php
    if (array_key_exists('s', $_GET)) {
    $description = join (" ", $_GET['attributes']);
    echo "You have a $description personality.";
    }
    ?>

    </body>
    </html>



    The heart of this code is the make_checkboxes( ) subroutine. It takes three arguments: the name for the group of checkboxes, the array of on-by-default values, and the array mapping values to descriptions. The list of options for the checkboxes is in the $personality_attributes array.




    7.4.8. File Uploads




    To handle file uploads

    (supported in most modern browsers), use the $_FILES array. Using the various authentication and file upload functions, you can control who is allowed to upload files and what to do with those files once they're on your system. Security concerns to take note of are described in Chapter 12.


    The following code displays a form that allows file uploads to the same page:



    <form enctype="multipart/form-data" action="<?= $PHP_SELF ?>" method="POST">
    <input type="hidden" name="MAX_FILE_SIZE" value="10240">
    File name: <input name="toProcess" type="file">
    <input type="submit" value="Upload">
    </form>



    The biggest problem with file uploads is the risk of getting a file that is too large to process. PHP has two ways of preventing this: a hard limit and a soft limit. The upload_max_filesize option in php.ini gives a hard upper limit on the size of uploaded files (it is set to 2 MB by default). If your form submits a parameter called MAX_FILE_SIZE before any file field parameters, PHP uses that value as the soft upper limit. For instance, in the previous example, the upper limit is set to 10 KB. PHP ignores attempts to set MAX_FILE_SIZE to a value larger than upload_max_filesize.


    Each element in $_FILES is itself an array, giving information about the uploaded file. The keys are:



    name


    The name of the file as supplied by the browser. It's difficult to make meaningful use of this, as the client machine may have different filename conventions than the web server (e.g., if the client is a Windows machine that tells you the file is D:\PHOTOS\ME.JPG, while the web server runs Unix, to which that path is meaningless).


    type


    The MIME type of the uploaded file as guessed at by the client.


    size


    The size of the uploaded file (in bytes). If the user attempted to upload a file that was too large, the size would be reported as 0.


    tmp_name


    The name of the temporary file on the server that holds the uploaded file. If the user attempted to upload a file that was too large, the name would be reported as "none".


    The correct way to test whether a file was successfully uploaded is to use the function is_uploaded_file( ), as follows:



    if (is_uploaded_file($_FILES['toProcess']['tmp_name']) {
    // successfully uploaded
    }



    Files are stored in the server's default temporary files directory, which is specified in php.ini with the upload_tmp_dir option. To move a file, use the move_uploaded_file( ) function:



    move_uploaded_file($_FILES['toProcess']['tmp_name'], "path/to/put/file/$file");



    The call to move_uploaded_file( ) automatically checks whether it was an uploaded file. When a script finishes, any files uploaded to that script are deleted from the temporary directory.




    7.4.9. Form Validation



    When you allow users to input data, you typically need to validate that data before using it or storing it for later use. There are several strategies available for validating data. The first is JavaScript on the client side. However, since the user can choose to turn JavaScript off, or may even be using a browser that doesn't support it, this cannot be the only validation

    you do.


    A more secure choice is to use PHP to do the validation. Example 7-9 shows a self-processing page with a form. The page allows the user to input a media item; three of the form elementsthe name, media type, and filenameare required. If the user neglects to give a value to any of them, the page is presented anew with a message detailing what's wrong. Any form fields the user already filled out are set to the values she entered. Finally, as an additional clue to the user, the text of the submit button changes from "Create" to "Continue" when the user is correcting the form.


    Example 7-9. Form validation




    <?php
    $name = $_POST['name'];
    $media_type = $_POST['media_type'];
    $filename = $_POST['filename'];
    $caption = $_POST['caption'];

    $tried = ($_POST['tried'] == 'yes');

    if ($tried) {
    $validated = (!empty($name) && !empty($media_type) && !empty($filename));

    if (!$validated) {
    ?>
    <p>
    The name, media type, and filename are required fields. Please fill
    them out to continue.
    </p>
    <?php
    }
    }

    if ($tried && $validated) {
    echo '<p>The item has been created.</p>';
    }

    // was this type of media selected? print "selected" if so
    function media_selected ($type) {
    global $media_type;
    if ($media_type == $type) { echo "selected"; }
    }
    ?>

    <form action="<?= $PHP_SELF ?>" method="POST">
    Name: <input type=text name="name" value="<?= $name ?>" /><br />
    Status: <input type="checkbox" name="status" value="active"
    <?php if($status == 'active') { echo 'checked'; } ?> /> Active<br />
    Media: <select name="media_type">
    <option value="">Choose one</option>
    <option value="picture" <?php media_selected('picture') ?> />Picture</option>
    <option value="audio" <?php media_selected('audio') ?> />Audio</option>
    <option value="movie" <?php media_selected('movie') ?> />Movie</option>
    </select><br />

    File: <input type="text" name="filename" value="<?= $filename ?>" /><br />
    Caption: <textarea name="caption"><?= $caption ?></textarea><br />

    <input type="hidden" name="tried" value="yes" />
    <input type="submit"
    value="<?php echo $tried ? 'Continue' : 'Create'; ?>" />
    </form>



    In this case, the validation is simply a check that a value was supplied. We set $validated to be true only if $name, $type, and $filename are all nonempty. Other possible validations include checking that an email address is valid or checking that the supplied filename is local and exists.


    For example, to validate an age field to ensure that it contains a nonnegative integer, use this code:



    $age = $_POST['age'];
    $valid_age = strspn($age, "1234567890") == strlen($age);



    The call to strspn( ) finds the number of digits at the start of the string. In a nonnegative integer, the whole string should be composed of digits, so it's a valid age if the entire string is made of digits. We could also have done this check with a regular expression:



    $valid_age = preg_match('/^\d+$/', $age);



    Validating email addresses is a nigh-impossible task. There's no way to take a string and see whether it corresponds to a valid email address. However, you can catch typos by requiring the user to enter the email address twice (into two different fields). You can also prevent people from entering email addresses like "me" or "me@aol" by requiring an at sign (@) and a period after it, and for bonus points you can check for domains to which you don't want to send mail (e.g., whitehouse.gov, or a competitor). For example:



    $email1 = strtolower($_POST['email1']);
    $email2 = strtolower($_POST['email2']);
    if ($email1 !== $email2) {
    die("The email addresses didn't match");
    }
    if (! preg_match('/@.+\..+$/', $email1)) {
    die("The email address is invalid");
    }
    if (strpos($email1, "whitehouse.gov")) {
    die("I will not send mail to the White House");
    }



    Field validation is basically string manipulation. In this example, we've used regular expressions and string functions to ensure that the string provided by the user is the type of string we expect.













    Wednesday, October 14, 2009

    Selecting R'EPOSITORIES'



    [ Team LiB ]





    Selecting REPOSITORIES


    There are five ENTITIES in the design that are roots of AGGREGATES, so we can limit our consideration to these, since none of the other objects is allowed to have REPOSITORIES.


    To decide which of these candidates should actually have a REPOSITORY, we must go back to the application requirements. In order to take a booking through the Booking Application, the user needs to select the Customer(s) playing the various roles (shipper, receiver, and so on). So we need a Customer Repository. We also need to find a Location to specify as the destination for the Cargo, so we create a Location Repository.


    The Activity Logging Application needs to allow the user to look up the Carrier Movement that a Cargo is being loaded onto, so we need a Carrier Movement Repository. This user must also tell the system which Cargo has been loaded, so we need a Cargo Repository.


    Figure 7.4. REPOSITORIES give access to selected AGGREGATE roots.


    For now there is no Handling Event Repository, because we decided to implement the association with Delivery History as a collection in the first iteration, and we have no application requirement to find out what has been loaded onto a Carrier Movement. Either of these reasons could change; if they did, then we would add a REPOSITORY.





      [ Team LiB ]



      6.6. Hyperlinks











       < Day Day Up > 







      6.6. Hyperlinks





      Did you know there is a web browser built into the Java editor? Well, there is�sort of. The editor lets you navigate around your program as if it were a web site. Hold down the Ctrl key and move your mouse through your source code. An underline will appear to indicate hyperlinked symbols. You can leave the mouse cursor over the symbol to see its definition, or click on it to open the declaration in the editor.



      Like a browser, Eclipse maintains a history of all the pages you've visited. Use the Back command ( ; Alt+Left; or Navigate Left) to go to the previous location, and use Forward ( ; Alt+Right; or Navigate Right) to go to the next one.















         < Day Day Up >