Source code: er/extensions/ERXDirectActionHyperlink.java
1 /*
2 * Copyright(C) NetStruxr, Inc. All rights reserved.
3 *
4 * This software is published under the terms of the NetStruxr
5 * Public Software License version 0.5, a copy of which has been
6 * included with this distribution in the LICENSE.NPL file. */
7 package er.extensions;
8
9 import com.webobjects.foundation.*;
10 import com.webobjects.eocontrol.*;
11 import com.webobjects.eoaccess.*;
12 import com.webobjects.appserver.*;
13 import java.util.*;
14
15 /**
16 * This component can be used for two things:<br/>
17 * 1) Generating direct action urls for use in
18 * components that are being e-mailed to people.
19 * 2) Support for encoding enterprise objects in
20 * the form values of generated urls.
21 * At the moment this component still contains some
22 * custy code that needs to be cleaned up before it
23 * can really be used, like adding the .wo and .api files ;0.<br/>
24 * <br/>
25 * Synopsis:<br/>
26 * [actionClass=<i>anActionClass</i>];directActionName=<i>aDirectActionName</i>;[entityNameSeparator=<i>aSeparator</i>;]
27 * [relative=<i>aBoolean</i>;][shouldEncryptObjectFormValues=<i>aBoolean</i>;][objectsForFormValues=<i>anArray</i>;]
28 * [bindingDictionary=<i>aDictionary</i>;][unencryptedBindingDictionary=<i>aDictionary</i>;]
29 *
30 * @binding actionClass direct action class to be used
31 * @binding directActionName direct action name
32 * @binding entityNameSeparator separator used when constructiong urls with encoded enterprise objects
33 * @binding relative generates relative or absolute url
34 * @binding shouldEncryptObjectFormValues boolean flag that tells if the primary keys
35 * of the enterprise objects should be encrypted using blowfish
36 * @binding objectForFormValue an enterprise object to be encoded in the url
37 * @binding objectsForFormValues array of enterprise objects to be encoded in the url
38 * @binding bindingDictionary adds the key-value pairs to generated url as
39 * form values, encrypting the values with blowfish.
40 * @binding unencryptedBindingDictionary adds the key-value pairs to generated url as
41 * form values
42 */
43 public class ERXDirectActionHyperlink extends ERXStatelessComponent {
44
45 // Class instances -------------------------------------------------
46
47 /** Key used to denote an adaptor prefix for a generated url string */
48 // MOVEME: ERXWOUtilities
49 public final static String ADAPTOR_PREFIX_MARKER="**ADAPTOR_PREFIX**";
50 /** Key used to denote a suffix for a generated url string */
51 // MOVEME: ERXWOUtilities
52 public final static String SUFFIX_MARKER="**SUFFIX**";
53
54 /** logging support */
55 public static final ERXLogger log = ERXLogger.getERXLogger(ERXDirectActionHyperlink.class);
56
57
58 // Constructor -------------------------------------------------
59 /**
60 * Public constructor
61 * @param aContext a context
62 */
63 public ERXDirectActionHyperlink(WOContext aContext) {
64 super(aContext);
65 }
66
67
68 // Component methods -------------------------------------------------
69
70 /**
71 * Cover method to return the binding: <b>entityNameSeparator</b>
72 * The entity name separator is used when constructing URLs with enterprise objects encoded in the url.
73 * This value default to the value defined in the system property <i>er.extensions.ERXDirectActionHyperlink.EntityNameSeparator</i> which defaults as well to the character '<pre>_</pre>'.
74 * @return returns the value for binding: <b>entityNameSeparator</b>
75 */
76 public String entityNameSeparator() {
77 String separator = (String)this.valueForBinding("entityNameSeparator");
78 if (separator == null)
79 separator = ERXEOEncodingUtilities.EntityNameSeparator;
80 return separator;
81 }
82
83 /**
84 * Cover method to return the boolean value
85 * of the binding: <b>relative</b>
86 * Defaults to <code>true</code>.
87 * @return returns if the generated url should be relative or not(absolute).
88 */
89 public boolean relative() {
90 return valueForBooleanBinding("relative", true);
91 }
92
93 /**
94 * Cover method to return the boolean value
95 * of the binding: <b>shouldEncryptObjectFormValues</b>
96 * Defaults to <code>false</code>.
97 * @return returns if the encoded objects' primary keys
98 * should be encrypted or not.
99 */
100 public boolean shouldEncryptObjectFormValues() {
101 return valueForBooleanBinding("shouldEncryptObjectFormValues");
102 }
103 /**
104 * Cover method to return the binding: <b>objectsForFormValues</b>
105 * This is an array of objects to be encoded as form values.
106 * @return returns bound array of objects to be encoded
107 */
108 public NSArray objectsForFormValues() {
109 return(NSArray)valueForBinding("objectsForFormValues");
110 }
111 /**
112 * Cover method to return the binding: <b>objectsForFormValue</b>
113 * This is an enterprise object to be encoded as form values.
114 * @return returns bound enterprise object to be encoded
115 */
116 public EOEnterpriseObject objectForFormValue() {
117 return(EOEnterpriseObject)valueForBinding("objectForFormValue");
118 }
119
120 /**
121 * Returns all of the objects to be encoded
122 * in the form values. Collects those bound
123 * to both 'objectsForFormValues' and
124 * 'objectForFormValue' into a single array.
125 * @return complete collection of objects to
126 * be encoded in form values.
127 */
128 public NSArray allObjectsForFormValues() {
129 NSMutableArray objects = null;
130 if(hasBinding("objectsForFormValues") || hasBinding("objectForFormValue")) {
131 objects = new NSMutableArray();
132 if(objectsForFormValues() != null)
133 objects.addObjectsFromArray(objectsForFormValues());
134 if(objectForFormValue() != null)
135 objects.addObject(objectForFormValue());
136 }
137 return objects != null ? objects : NSArray.EmptyArray;
138 }
139
140 /**
141 * Retrives a given binding and if it is not null
142 * then returns <code>toString</code> called on the
143 * bound object.
144 * @param binding to be resolved
145 * @return resolved binding in string format
146 */
147 // MOVEME: Should move to ERXStatelessComponent and have this component subclass that
148 // FIXME: Should be renamed stringValueForBinding
149 public String stringForBinding(String binding) {
150 Object v=valueForBinding(binding);
151 return v!=null ? v.toString() : null;
152 }
153
154 /**
155 * Generates an href for the given direct action based
156 * on all of the bindings. Currently it generates an
157 * absolute url starting with the key: ADAPTOR_PREFIX_MARKER.
158 * Before this href can be really useful it needs to
159 * be cleaned up.
160 * @return href containing all of the specification from
161 * the bindings.
162 */
163 // FIXME: Lots of stuff to be fixed here.
164 public String href() {
165 String directActionName = null;
166 NSDictionary encryptedBindingDict = null;
167 NSDictionary unencryptedBindingDict = null;
168 NSArray formValuesObjects = null;
169
170 // Compose the direct action name from the bindings
171 // Typically, something like "DirectActionClass/actionMethod".
172 // Keep consistency with directActionName semantics as it is defined in directActionHref static method
173 if(this.hasBinding("actionClass")) {
174 StringBuffer daBuffer = new StringBuffer();
175 daBuffer.append(this.valueForBinding("actionClass"));
176 daBuffer.append('/');
177 daBuffer.append(this.valueForBinding("directActionName"));
178 directActionName = daBuffer.toString();
179 } else {
180 directActionName = (String)this.valueForBinding("directActionName");
181 }
182
183 if((directActionName == null) || (directActionName.length() == 0))
184 throw new IllegalArgumentException("ERXDirectActionHyperlink: directActionName must be specified.");
185
186 // Get the binding dictionaries
187 // FIXME: Rename binding to encryptedBindingDictionary
188 if(hasBinding("bindingDictionary"))
189 encryptedBindingDict = (NSDictionary)valueForBinding("bindingDictionary");
190
191 if(hasBinding("unencryptedBindingDictionary"))
192 unencryptedBindingDict = (NSDictionary)valueForBinding("unencryptedBindingDictionary");
193
194 // Get the objects to encode
195 if(allObjectsForFormValues().count() > 0)
196 formValuesObjects = allObjectsForFormValues();
197
198 // Compose and return the final url
199 return directActionHyperlink(this.context(),
200 this.shouldEncryptObjectFormValues(), formValuesObjects, entityNameSeparator(),
201 encryptedBindingDict, unencryptedBindingDict,
202 this.application().name(), directActionName,
203 this.relative(), null);
204 }
205
206 // Class methods -------------------------------------------------
207
208 /** Holds the application host url */
209 // MOVEME: This stuff might be better served if it was off of ERXApplication
210 private static String _applicationHostUrl;
211 /**
212 * This returns the value stored in the system properties:
213 * <b>ERApplicationHostURL</b> if this isn't set then a
214 * runtime exception is thrown. This property should be of
215 * the form: http://mymachine.com
216 * @return the application host url that should be used when
217 * complete urls are generated.
218 */
219 // MOVEME: This stuff might be better served if it was off of ERXApplication
220 public static String applicationHostUrl() {
221 if(_applicationHostUrl ==null) {
222 // FIXME: Should be: er.extensions.ERXApplicationHostURL
223 _applicationHostUrl = System.getProperty("ERApplicationHostURL");
224 if(_applicationHostUrl==null || _applicationHostUrl.length()==0)
225 throw new RuntimeException("The ERApplicationHostURL default was empty -- please set it for the machine running the target application: it should look like http://mymachine.com");
226 }
227 return _applicationHostUrl;
228 }
229
230 public static String directActionHyperlink(WOContext context,
231 boolean encryptEos, NSArray eos, String entityNameSeparator,
232 NSDictionary encryptedDict, NSDictionary unencryptedDict,
233 String appName, String daName,
234 boolean relative, String suffix) {
235 StringBuffer result = new StringBuffer(ADAPTOR_PREFIX_MARKER);
236 result.append(".woa/wa/");
237 result.append(daName);
238 result.append('?');
239
240 if(encryptedDict != null) {
241 NSArray allKeys = encryptedDict.allKeys();
242 for(Enumeration e = allKeys.objectEnumerator(); e.hasMoreElements();) {
243 String key =(String)e.nextElement();
244 String value = encryptedDict.objectForKey(key).toString();
245 ERXStringUtilities.appendSeparatorIfLastNot('&', '?', result);
246 result.append(key);
247 result.append("=");
248 result.append(ERXCrypto.blowfishEncode(value));
249 }
250 }
251
252 if(unencryptedDict != null) {
253 NSArray allKeys = unencryptedDict.allKeys();
254 for(Enumeration e = allKeys.objectEnumerator(); e.hasMoreElements();) {
255 String key =(String)e.nextElement();
256 String value = unencryptedDict.objectForKey(key).toString();
257 ERXStringUtilities.appendSeparatorIfLastNot('&', '?', result);
258 result.append(key);
259 result.append("=");
260 result.append(value);
261 }
262 }
263
264 if((eos != null) &&(eos.count() > 0)) {
265 ERXStringUtilities.appendSeparatorIfLastNot('&', '?', result);
266 result.append(ERXEOEncodingUtilities.encodeEnterpriseObjectsPrimaryKeyForUrl(eos, entityNameSeparator, encryptEos));
267 }
268
269 if(suffix != null)
270 result.append(SUFFIX_MARKER);
271
272 return completeURLFromString(result.toString(), context, appName, relative, suffix);
273 }
274
275 /**
276 * This method is useful for completing urls that are being generated
277 * in components that are going to be e-mailed to users. This method
278 * has the ability to substitute different application names which
279 * can be helpful if one application is generating the component, but
280 * the action of the url points to a different application on the
281 * same host.
282 * @param s href string to be completed
283 * @param c current context
284 * @param applicationName to be substituted if ADAPTOR_PREFIX_MARKER
285 * is present
286 * @param relative flag to indicate if the generated url should be
287 * relative or absolute in which case the applicationHostUrl
288 * will be used
289 * @param suffix string to be substitued if the SUFFIX_MARKER string
290 * is present
291 * @return complete url after substitutions have been made
292 */
293 // MOVEME: ERXWOUtilities
294 public static String completeURLFromString(String s,
295 WOContext c,
296 String applicationName,
297 boolean relative,
298 String suffix) {
299 if(s!=null && s.indexOf(ADAPTOR_PREFIX_MARKER)!=-1) {
300 if(applicationName==null || applicationName.length()==0)
301 throw new RuntimeException("completeURLFromString: found ADAPTOR_PREFIX_MARKER and no application name to replace it - original text:"+s);
302 NSArray a=NSArray.componentsSeparatedByString(s, ADAPTOR_PREFIX_MARKER);
303 // BIG ASSUMPTION : the target application must have the same suffix as this application
304 String postFix=c.request().adaptorPrefix()+"/"+applicationName;
305 s= a.componentsJoinedByString(relative ? postFix : applicationHostUrl()+postFix);
306 }
307 if(s!=null && s.indexOf(SUFFIX_MARKER)!=-1) {
308 NSArray a=NSArray.componentsSeparatedByString(s, SUFFIX_MARKER);
309 // BIG ASSUMPTION : the target application must have the same suffix as this application
310 String postFix=suffix!=null ? suffix : "";
311 s= a.componentsJoinedByString(postFix);
312 }
313 return s;
314 }
315 }