001    /**
002     * Copyright 2005-2012 The Kuali Foundation
003     *
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.opensource.org/licenses/ecl2.php
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.rice.kns.util;
017    
018    import java.io.ByteArrayOutputStream;
019    import java.io.IOException;
020    import java.io.InputStream;
021    import java.io.OutputStream;
022    import java.util.Arrays;
023    import java.util.Collection;
024    import java.util.Enumeration;
025    import java.util.HashMap;
026    import java.util.Hashtable;
027    import java.util.Iterator;
028    import java.util.LinkedHashMap;
029    import java.util.List;
030    import java.util.Map;
031    import java.util.Set;
032    import java.util.regex.Matcher;
033    import java.util.regex.Pattern;
034    
035    import javax.servlet.ServletException;
036    import javax.servlet.http.HttpServletRequest;
037    import javax.servlet.http.HttpServletResponse;
038    import javax.servlet.http.HttpSession;
039    import javax.servlet.jsp.PageContext;
040    
041    import org.apache.commons.lang.StringEscapeUtils;
042    import org.apache.commons.lang.StringUtils;
043    import org.apache.log4j.Level;
044    import org.apache.log4j.Logger;
045    import org.apache.struts.Globals;
046    import org.apache.struts.action.ActionForm;
047    import org.apache.struts.action.ActionMapping;
048    import org.apache.struts.action.ActionServletWrapper;
049    import org.apache.struts.upload.CommonsMultipartRequestHandler;
050    import org.apache.struts.upload.FormFile;
051    import org.apache.struts.upload.MultipartRequestHandler;
052    import org.apache.struts.upload.MultipartRequestWrapper;
053    import org.kuali.rice.core.api.config.property.ConfigurationService;
054    import org.kuali.rice.core.api.util.RiceKeyConstants;
055    import org.kuali.rice.kew.api.action.ActionRequest;
056    import org.kuali.rice.kew.api.action.RecipientType;
057    import org.kuali.rice.kim.api.role.Role;
058    import org.kuali.rice.kim.api.services.KimApiServiceLocator;
059    import org.kuali.rice.kns.datadictionary.KNSDocumentEntry;
060    import org.kuali.rice.kns.datadictionary.MaintenanceDocumentEntry;
061    import org.kuali.rice.kns.document.authorization.DocumentAuthorizer;
062    import org.kuali.rice.kns.service.KNSServiceLocator;
063    import org.kuali.rice.kns.web.struts.action.KualiAction;
064    import org.kuali.rice.kns.web.struts.action.KualiMultipartRequestHandler;
065    import org.kuali.rice.kns.web.struts.form.InquiryForm;
066    import org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase;
067    import org.kuali.rice.kns.web.struts.form.KualiForm;
068    import org.kuali.rice.kns.web.struts.form.KualiMaintenanceForm;
069    import org.kuali.rice.kns.web.struts.form.pojo.PojoFormBase;
070    import org.kuali.rice.kns.web.ui.Field;
071    import org.kuali.rice.kns.web.ui.Row;
072    import org.kuali.rice.kns.web.ui.Section;
073    import org.kuali.rice.krad.datadictionary.AttributeDefinition;
074    import org.kuali.rice.krad.datadictionary.AttributeSecurity;
075    import org.kuali.rice.krad.datadictionary.DataDictionary;
076    import org.kuali.rice.krad.datadictionary.DataDictionaryEntryBase;
077    import org.kuali.rice.krad.datadictionary.mask.MaskFormatter;
078    import org.kuali.rice.krad.document.Document;
079    import org.kuali.rice.krad.exception.ValidationException;
080    import org.kuali.rice.krad.service.KRADServiceLocator;
081    import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
082    import org.kuali.rice.krad.util.GlobalVariables;
083    import org.kuali.rice.krad.util.KRADConstants;
084    import org.kuali.rice.krad.util.MessageMap;
085    import org.kuali.rice.krad.util.ObjectUtils;
086    
087    /**
088     * General helper methods for handling requests.
089     */
090    public class WebUtils {
091            private static final Logger LOG = Logger.getLogger(WebUtils.class);
092    
093            private static final String IMAGE_COORDINATE_CLICKED_X_EXTENSION = ".x";
094            private static final String IMAGE_COORDINATE_CLICKED_Y_EXTENSION = ".y";
095    
096            private static final String APPLICATION_IMAGE_URL_PROPERTY_PREFIX = "application.custom.image.url";
097            private static final String DEFAULT_IMAGE_URL_PROPERTY_NAME = "kr.externalizable.images.url";
098    
099        /**
100         * Prefixes indicating an absolute url
101         */
102        private static final String[] SCHEMES = { "http://", "https://" };
103    
104            /**
105             * A request attribute name that indicates that a
106             * {@link org.kuali.rice.kns.exception.FileUploadLimitExceededException} has already been thrown for the
107             * request.
108             */
109            public static final String FILE_UPLOAD_LIMIT_EXCEEDED_EXCEPTION_ALREADY_THROWN = "fileUploadLimitExceededExceptionAlreadyThrown";
110    
111            private static ConfigurationService configurationService;
112    
113            /**
114             * Checks for methodToCall parameter, and picks off the value using set dot
115             * notation. Handles the problem of image submits.
116             * 
117             * @param request
118             * @return methodToCall String
119             */
120            public static String parseMethodToCall(ActionForm form, HttpServletRequest request) {
121                    String methodToCall = null;
122    
123                    // check if is specified cleanly
124                    if (StringUtils.isNotBlank(request.getParameter(KRADConstants.DISPATCH_REQUEST_PARAMETER))) {
125                            if (form instanceof KualiForm
126                                            && !((KualiForm) form).shouldMethodToCallParameterBeUsed(KRADConstants.DISPATCH_REQUEST_PARAMETER,
127                                                            request.getParameter(KRADConstants.DISPATCH_REQUEST_PARAMETER), request)) {
128                                    throw new RuntimeException("Cannot verify that the methodToCall should be "
129                                                    + request.getParameter(KRADConstants.DISPATCH_REQUEST_PARAMETER));
130                            }
131                            methodToCall = request.getParameter(KRADConstants.DISPATCH_REQUEST_PARAMETER);
132                            // include .x at the end of the parameter to make it consistent w/
133                            // other parameters
134                            request.setAttribute(KRADConstants.METHOD_TO_CALL_ATTRIBUTE, KRADConstants.DISPATCH_REQUEST_PARAMETER + "."
135                                            + methodToCall + IMAGE_COORDINATE_CLICKED_X_EXTENSION);
136                    }
137    
138                    /**
139                     * The reason why we are checking for a ".x" at the end of the parameter
140                     * name: It is for the image names that in addition to sending the form
141                     * data, the web browser sends the x,y coordinate of where the user
142                     * clicked on the image. If the image input is not given a name then the
143                     * browser sends the x and y coordinates as the "x" and "y" input
144                     * fields. If the input image does have a name, the x and y coordinates
145                     * are sent using the format name.x and name.y.
146                     */
147                    if (methodToCall == null) {
148                            // iterate through parameters looking for methodToCall
149                            for (Enumeration i = request.getParameterNames(); i.hasMoreElements();) {
150                                    String parameterName = (String) i.nextElement();
151    
152                                    // check if the parameter name is a specifying the methodToCall
153                                    if (isMethodToCall(parameterName)) {
154                                            methodToCall = getMethodToCallSettingAttribute(form, request, parameterName);
155                                            break;
156                                    }
157                                    else {
158                                            // KULRICE-1218: Check if the parameter's values match (not
159                                            // just the name)
160                                            for (String value : request.getParameterValues(parameterName)) {
161                                                    // adding period to startsWith check - don't want to get
162                                                    // confused with methodToCallFoobar
163                                                    if (isMethodToCall(value)) {
164                                                            methodToCall = getMethodToCallSettingAttribute(form, request, value);
165                                                            // why is there not a break outer loop here?
166                                                    }
167                                            }
168                                    }
169                            }
170                    }
171    
172                    return methodToCall;
173            }
174    
175        /**
176             * Checks if a string signifies a methodToCall string
177             * 
178             * @param string
179             *            the string to check
180             * @return true if is a methodToCall
181             */
182            private static boolean isMethodToCall(String string) {
183                    // adding period to startsWith check - don't want to get confused with
184                    // methodToCallFoobar
185                    return string.startsWith(KRADConstants.DISPATCH_REQUEST_PARAMETER + ".");
186            }
187    
188            /**
189             * Parses out the methodToCall command and also sets the request attribute
190             * for the methodToCall.
191             * 
192             * @param form
193             *            the ActionForm
194             * @param request
195             *            the request to set the attribute on
196             * @param string
197             *            the methodToCall string
198             * @return the methodToCall command
199             */
200            private static String getMethodToCallSettingAttribute(ActionForm form, HttpServletRequest request, String string) {
201    
202                    if (form instanceof KualiForm
203                                    && !((KualiForm) form).shouldMethodToCallParameterBeUsed(string, request.getParameter(string), request)) {
204                            throw new RuntimeException("Cannot verify that the methodToCall should be " + string);
205                    }
206                    // always adding a coordinate even if not an image
207                    final String attributeValue = endsWithCoordinates(string) ? string : string
208                                    + IMAGE_COORDINATE_CLICKED_X_EXTENSION;
209                    final String methodToCall = StringUtils.substringBetween(attributeValue,
210                                    KRADConstants.DISPATCH_REQUEST_PARAMETER + ".", ".");
211                    request.setAttribute(KRADConstants.METHOD_TO_CALL_ATTRIBUTE, attributeValue);
212                    return methodToCall;
213            }
214    
215            /**
216             * Iterates through and logs (at the given level) all attributes and
217             * parameters of the given request onto the given Logger
218             * 
219             * @param request
220             * @param logger
221             */
222            public static void logRequestContents(Logger logger, Level level, HttpServletRequest request) {
223                    if (logger.isEnabledFor(level)) {
224                            logger.log(level, "--------------------");
225                            logger.log(level, "HttpRequest attributes:");
226                            for (Enumeration e = request.getAttributeNames(); e.hasMoreElements();) {
227                                    String attrName = (String) e.nextElement();
228                                    Object attrValue = request.getAttribute(attrName);
229    
230                                    if (attrValue.getClass().isArray()) {
231                                            logCollection(logger, level, attrName, Arrays.asList((Object[]) attrValue));
232                                    }
233                                    else if (attrValue instanceof Collection) {
234                                            logCollection(logger, level, attrName, (Collection) attrValue);
235                                    }
236                                    else if (attrValue instanceof Map) {
237                                            logMap(logger, level, attrName, (Map) attrValue);
238                                    }
239                                    else {
240                                            logObject(logger, level, attrName, attrValue);
241                                    }
242                            }
243    
244                            logger.log(level, "--------------------");
245                            logger.log(level, "HttpRequest parameters:");
246                            for (Enumeration i = request.getParameterNames(); i.hasMoreElements();) {
247                                    String paramName = (String) i.nextElement();
248                                    String[] paramValues = (String[]) request.getParameterValues(paramName);
249    
250                                    logArray(logger, level, paramName, paramValues);
251                            }
252    
253                            logger.log(level, "--------------------");
254                    }
255            }
256    
257            private static void logArray(Logger logger, Level level, String arrayName, Object[] array) {
258                    StringBuffer value = new StringBuffer("[");
259                    for (int i = 0; i < array.length; ++i) {
260                            if (i > 0) {
261                                    value.append(",");
262                            }
263                            value.append(array[i]);
264                    }
265                    value.append("]");
266    
267                    logThing(logger, level, arrayName, value);
268            }
269    
270            private static void logCollection(Logger logger, Level level, String collectionName, Collection c) {
271                    StringBuffer value = new StringBuffer("{");
272                    for (Iterator i = c.iterator(); i.hasNext();) {
273                            value.append(i.next());
274                            if (i.hasNext()) {
275                                    value.append(",");
276                            }
277                    }
278                    value.append("}");
279    
280                    logThing(logger, level, collectionName, value);
281            }
282    
283            private static void logMap(Logger logger, Level level, String mapName, Map m) {
284                    StringBuffer value = new StringBuffer("{");
285                    for (Iterator i = m.entrySet().iterator(); i.hasNext();) {
286                            Map.Entry e = (Map.Entry) i.next();
287                            value.append("('" + e.getKey() + "','" + e.getValue() + "')");
288                    }
289                    value.append("}");
290    
291                    logThing(logger, level, mapName, value);
292            }
293    
294            private static void logObject(Logger logger, Level level, String objectName, Object o) {
295                    logThing(logger, level, objectName, "'" + o + "'");
296            }
297    
298            private static void logThing(Logger logger, Level level, String thingName, Object thing) {
299                    logger.log(level, "    '" + thingName + "' => " + thing);
300            }
301    
302            /**
303             * A file that is not of type text/plain or text/html can be output through
304             * the response using this method.
305             * 
306             * @param response
307             * @param contentType
308             * @param byteArrayOutputStream
309             * @param fileName
310             */
311            public static void saveMimeOutputStreamAsFile(HttpServletResponse response, String contentType,
312                            ByteArrayOutputStream byteArrayOutputStream, String fileName) throws IOException {
313    
314                    // set response
315                    response.setContentType(contentType);
316                    response.setHeader("Content-disposition", "attachment; filename=\"" + fileName + "\"");
317                    response.setHeader("Expires", "0");
318                    response.setHeader("Cache-Control", "must-revalidate, post-check=0, pre-check=0");
319                    response.setHeader("Pragma", "public");
320                    response.setContentLength(byteArrayOutputStream.size());
321    
322                    // write to output
323                    OutputStream outputStream = response.getOutputStream();
324                    byteArrayOutputStream.writeTo(response.getOutputStream());
325                    outputStream.flush();
326                    outputStream.close();
327            }
328    
329            /**
330             * A file that is not of type text/plain or text/html can be output through
331             * the response using this method.
332             * 
333             * @param response
334             * @param contentType
335             * @param inStream
336             * @param fileName
337             */
338            public static void saveMimeInputStreamAsFile(HttpServletResponse response, String contentType,
339                            InputStream inStream, String fileName, int fileSize) throws IOException {
340    
341                    // set response
342                    response.setContentType(contentType);
343                    response.setHeader("Content-disposition", "attachment; filename=\"" + fileName + "\"");
344                    response.setHeader("Expires", "0");
345                    response.setHeader("Cache-Control", "must-revalidate, post-check=0, pre-check=0");
346                    response.setHeader("Pragma", "public");
347                    response.setContentLength(fileSize);
348    
349                    // write to output
350                    OutputStream out = response.getOutputStream();
351                    while (inStream.available() > 0) {
352                            out.write(inStream.read());
353                    }
354                    out.flush();
355            }
356    
357            /**
358             * JSTL function to return the tab state of the tab from the form.
359             * 
360             * @param form
361             * @param tabKey
362             * @return
363             */
364            public static String getTabState(KualiForm form, String tabKey) {
365                    return form.getTabState(tabKey);
366            }
367    
368            public static void incrementTabIndex(KualiForm form, String tabKey) {
369                    form.incrementTabIndex();
370            }
371    
372        /**
373         * Attempts to reopen sub tabs which would have been closed for inactive records
374         *
375         * @param sections the list of Sections whose rows and fields to set the open tab state on
376         * @param tabStates the map of tabKey->tabState.  This map will be modified to set entries to "OPEN"
377         * @param collectionName the name of the collection reopening
378         */
379        public static void reopenInactiveRecords(List<Section> sections, Map<String, String> tabStates, String collectionName) {
380            for (Section section : sections) {
381                for (Row row: section.getRows()) {
382                    for (Field field : row.getFields()) {
383                        if (field != null) {
384                            if (Field.CONTAINER.equals(field.getFieldType()) && StringUtils.startsWith(field.getContainerName(), collectionName)) {
385                                final String tabKey = WebUtils.generateTabKey(FieldUtils.generateCollectionSubTabName(field));
386                                tabStates.put(tabKey, KualiForm.TabState.OPEN.name());
387                            }
388                        }
389                    }
390                }
391            }
392        }
393    
394            /**
395             * Generates a String from the title that can be used as a Map key.
396             * 
397             * @param tabTitle
398             * @return
399             */
400            public static String generateTabKey(String tabTitle) {
401                    String key = "";
402                    if (!StringUtils.isBlank(tabTitle)) {
403                            key = tabTitle.replaceAll("\\W", "");
404                            // if (key.length() > 25) {
405                            // key = key.substring(0, 24);
406                            // }
407                    }
408    
409                    return key;
410            }
411    
412            public static void getMultipartParameters(HttpServletRequest request, ActionServletWrapper servletWrapper,
413                            ActionForm form, ActionMapping mapping) {
414                    Map params = new HashMap();
415    
416                    // Get the ActionServletWrapper from the form bean
417                    // ActionServletWrapper servletWrapper = getServletWrapper();
418    
419                    try {
420                            CommonsMultipartRequestHandler multipartHandler = new CommonsMultipartRequestHandler();
421                            if (multipartHandler != null) {
422                                    // Set servlet and mapping info
423                                    if (servletWrapper != null) {
424                                            // from pojoformbase
425                                            // servlet only affects tempdir on local disk
426                                            servletWrapper.setServletFor(multipartHandler);
427                                    }
428                                    multipartHandler.setMapping((ActionMapping) request.getAttribute(Globals.MAPPING_KEY));
429                                    // Initialize multipart request class handler
430                                    multipartHandler.handleRequest(request);
431    
432                                    Collection<FormFile> files = multipartHandler.getFileElements().values();
433                                    Enumeration keys = multipartHandler.getFileElements().keys();
434    
435                                    while (keys.hasMoreElements()) {
436                                            Object key = keys.nextElement();
437                                            FormFile file = (FormFile) multipartHandler.getFileElements().get(key);
438                                            long maxSize = WebUtils.getMaxUploadSize(form);
439                                            if (LOG.isDebugEnabled()) {
440                                                    LOG.debug(file.getFileSize());
441                                            }
442                                            if (maxSize > 0 && Long.parseLong(file.getFileSize() + "") > maxSize) {
443    
444                                                    GlobalVariables.getMessageMap().putError(key.toString(),
445                                                                    RiceKeyConstants.ERROR_UPLOADFILE_SIZE,
446                                                                    new String[] { file.getFileName(), Long.toString(maxSize) });
447    
448                                            }
449                                    }
450    
451                                    // get file elements for kualirequestprocessor
452                                    if (servletWrapper == null) {
453                                            request.setAttribute(KRADConstants.UPLOADED_FILE_REQUEST_ATTRIBUTE_KEY,
454                                                            getFileParametersForMultipartRequest(request, multipartHandler));
455                                    }
456                            }
457                    }
458                    catch (ServletException e) {
459                            throw new ValidationException("unable to handle multipart request " + e.getMessage(), e);
460                    }
461            }
462    
463            public static long getMaxUploadSize(ActionForm form) {
464                    long max = 0L;
465                    KualiMultipartRequestHandler multipartHandler = new KualiMultipartRequestHandler();
466                    if (form instanceof PojoFormBase) {
467                            max = multipartHandler.calculateMaxUploadSizeToMaxOfList(((PojoFormBase) form).getMaxUploadSizes());
468                    }
469                    if (LOG.isDebugEnabled()) {
470                            LOG.debug("Max File Upload Size: " + max);
471                    }
472                    return max;
473            }
474    
475            private static Map getFileParametersForMultipartRequest(HttpServletRequest request,
476                            MultipartRequestHandler multipartHandler) {
477                    Map parameters = new HashMap();
478                    Hashtable elements = multipartHandler.getFileElements();
479                    Enumeration e = elements.keys();
480                    while (e.hasMoreElements()) {
481                            String key = (String) e.nextElement();
482                            parameters.put(key, elements.get(key));
483                    }
484    
485                    if (request instanceof MultipartRequestWrapper) {
486                            request = (HttpServletRequest) ((MultipartRequestWrapper) request).getRequest();
487                            e = request.getParameterNames();
488                            while (e.hasMoreElements()) {
489                                    String key = (String) e.nextElement();
490                                    parameters.put(key, request.getParameterValues(key));
491                            }
492                    }
493                    else {
494                            LOG.debug("Gathering multipart parameters for unwrapped request");
495                    }
496                    return parameters;
497            }
498    
499            // end multipart
500    
501            public static void registerEditableProperty(PojoFormBase form, String editablePropertyName) {
502                    form.registerEditableProperty(editablePropertyName);
503            }
504    
505            public static boolean isDocumentSession(Document document, PojoFormBase docForm) {
506                    boolean sessionDoc = document instanceof org.kuali.rice.krad.document.SessionDocument;
507                    boolean dataDictionarySessionDoc = false;
508                    if (!sessionDoc) {
509                            DataDictionary dataDictionary = KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary();
510                            if (docForm instanceof KualiMaintenanceForm) {
511                                    KualiMaintenanceForm maintenanceForm = (KualiMaintenanceForm) docForm;
512                                    if (dataDictionary != null) {
513                                            if (maintenanceForm.getDocTypeName() != null) {
514                            MaintenanceDocumentEntry maintenanceDocumentEntry = (MaintenanceDocumentEntry) dataDictionary.getDocumentEntry(maintenanceForm.getDocTypeName());
515                                                    dataDictionarySessionDoc = maintenanceDocumentEntry.isSessionDocument();
516                                            }
517                                    }
518                            }
519                            else {
520                                    if (document != null && dataDictionary != null) {
521                                            KNSDocumentEntry documentEntry = (KNSDocumentEntry) dataDictionary.getDocumentEntry(document.getClass().getName());
522                                            dataDictionarySessionDoc = documentEntry.isSessionDocument();
523                                    }
524                            }
525                    }
526                    return sessionDoc || dataDictionarySessionDoc;
527            }
528    
529            public static boolean isFormSessionDocument(PojoFormBase form) {
530                    Document document = null;
531                    if (KualiDocumentFormBase.class.isAssignableFrom(form.getClass())) {
532                            KualiDocumentFormBase docForm = (KualiDocumentFormBase) form;
533                            document = docForm.getDocument();
534                    }
535                    return isDocumentSession(document, form);
536            }
537    
538            public static String KEY_KUALI_FORM_IN_SESSION = "KualiForm";
539    
540            public static ActionForm getKualiForm(PageContext pageContext) {
541                    return getKualiForm((HttpServletRequest) pageContext.getRequest());
542            }
543    
544            public static ActionForm getKualiForm(HttpServletRequest request) {
545                    if (request.getAttribute(KEY_KUALI_FORM_IN_SESSION) != null) {
546                            return (ActionForm) request.getAttribute(KEY_KUALI_FORM_IN_SESSION);
547                    }
548                    else {
549                            final HttpSession session = request.getSession(false);
550                            return session != null ? (ActionForm) session.getAttribute(KEY_KUALI_FORM_IN_SESSION) : null;
551                    }
552            }
553    
554            public static boolean isPropertyEditable(Set<String> editableProperties, String propertyName) {
555                    if (LOG.isDebugEnabled()) {
556                            LOG.debug("isPropertyEditable(" + propertyName + ")");
557                    }
558    
559                    boolean returnVal = editableProperties == null
560                                    || editableProperties.contains(propertyName)
561                                    || (getIndexOfCoordinateExtension(propertyName) == -1 ? false : editableProperties
562                                                    .contains(propertyName.substring(0, getIndexOfCoordinateExtension(propertyName))));
563                    if (!returnVal) {
564                            if (LOG.isDebugEnabled()) {
565                                    LOG.debug("isPropertyEditable(" + propertyName + ") == false / editableProperties: "
566                                                    + editableProperties);
567                            }
568                    }
569                    return returnVal;
570            }
571    
572            public static boolean endsWithCoordinates(String parameter) {
573                    return parameter.endsWith(WebUtils.IMAGE_COORDINATE_CLICKED_X_EXTENSION)
574                                    || parameter.endsWith(WebUtils.IMAGE_COORDINATE_CLICKED_Y_EXTENSION);
575            }
576    
577            public static int getIndexOfCoordinateExtension(String parameter) {
578                    int indexOfCoordinateExtension = parameter.lastIndexOf(WebUtils.IMAGE_COORDINATE_CLICKED_X_EXTENSION);
579            if (indexOfCoordinateExtension == -1) {
580                indexOfCoordinateExtension = parameter.lastIndexOf(WebUtils.IMAGE_COORDINATE_CLICKED_Y_EXTENSION);
581            }
582                    return indexOfCoordinateExtension;
583            }
584    
585        public static boolean isInquiryHiddenField(String className, String fieldName, Object formObject, String propertyName) {
586            boolean isHidden = false;
587            String hiddenInquiryFields = getKualiConfigurationService().getPropertyValueAsString(className + ".hidden");
588            if (StringUtils.isEmpty(hiddenInquiryFields)) {
589                    return isHidden;
590            }
591            List hiddenFields = Arrays.asList(hiddenInquiryFields.replaceAll(" ", "").split(","));
592            if (hiddenFields.contains(fieldName.trim())) {
593                    isHidden = true;
594            }
595            return isHidden;
596        }
597    
598        public static boolean isHiddenKimObjectType(String type, String configParameter) {
599            boolean hideType = false;
600            String hiddenTypes = getKualiConfigurationService().getPropertyValueAsString(configParameter);
601            if (StringUtils.isEmpty(hiddenTypes)) {
602                    return hideType;
603            }
604            List hiddenTypeValues = Arrays.asList(hiddenTypes.replaceAll(" ", "").split(","));
605            if (hiddenTypeValues.contains(type.trim())) {
606                    hideType = true;
607            }
608            return hideType;
609        }
610    
611            public static String getFullyMaskedValue(String className, String fieldName, Object formObject, String propertyName) {
612                    String displayMaskValue = null;
613                    Object propertyValue = ObjectUtils.getPropertyValue(formObject, propertyName);
614    
615                    DataDictionaryEntryBase entry = (DataDictionaryEntryBase) KRADServiceLocatorWeb.getDataDictionaryService()
616                                    .getDataDictionary().getDictionaryObjectEntry(className);
617                    AttributeDefinition a = entry.getAttributeDefinition(fieldName);
618    
619                    AttributeSecurity attributeSecurity = a.getAttributeSecurity();
620                    if (attributeSecurity != null && attributeSecurity.isMask()) {
621                            MaskFormatter maskFormatter = attributeSecurity.getMaskFormatter();
622                            displayMaskValue = maskFormatter.maskValue(propertyValue);
623    
624                    }
625                    return displayMaskValue;
626            }
627    
628            public static String getPartiallyMaskedValue(String className, String fieldName, Object formObject,
629                            String propertyName) {
630                    String displayMaskValue = null;
631                    Object propertyValue = ObjectUtils.getPropertyValue(formObject, propertyName);
632    
633                    DataDictionaryEntryBase entry = (DataDictionaryEntryBase) KRADServiceLocatorWeb.getDataDictionaryService()
634                                    .getDataDictionary().getDictionaryObjectEntry(className);
635                    AttributeDefinition a = entry.getAttributeDefinition(fieldName);
636    
637                    AttributeSecurity attributeSecurity = a.getAttributeSecurity();
638                    if (attributeSecurity != null && attributeSecurity.isPartialMask()) {
639                            MaskFormatter partialMaskFormatter = attributeSecurity.getPartialMaskFormatter();
640                            displayMaskValue = partialMaskFormatter.maskValue(propertyValue);
641    
642                    }
643                    return displayMaskValue;
644            }
645    
646            public static boolean canFullyUnmaskField(String businessObjectClassName, String fieldName, KualiForm form) {
647                    Class businessObjClass = null;
648                    try {
649                            businessObjClass = Class.forName(businessObjectClassName);
650                    }
651                    catch (Exception e) {
652                            throw new RuntimeException("Unable to resolve class name: " + businessObjectClassName);
653                    }
654                    if (form instanceof KualiDocumentFormBase) {
655                            return KNSServiceLocator.getBusinessObjectAuthorizationService().canFullyUnmaskField(
656                                            GlobalVariables.getUserSession().getPerson(), businessObjClass, fieldName,
657                                            ((KualiDocumentFormBase) form).getDocument());
658                    }
659                    else {
660                            return KNSServiceLocator.getBusinessObjectAuthorizationService().canFullyUnmaskField(
661                                            GlobalVariables.getUserSession().getPerson(), businessObjClass, fieldName, null);
662                    }
663            }
664    
665            public static boolean canPartiallyUnmaskField(String businessObjectClassName, String fieldName, KualiForm form) {
666                    Class businessObjClass = null;
667                    try {
668                            businessObjClass = Class.forName(businessObjectClassName);
669                    }
670                    catch (Exception e) {
671                            throw new RuntimeException("Unable to resolve class name: " + businessObjectClassName);
672                    }
673                    if (form instanceof KualiDocumentFormBase) {
674                            return KNSServiceLocator.getBusinessObjectAuthorizationService().canPartiallyUnmaskField(
675                                            GlobalVariables.getUserSession().getPerson(), businessObjClass, fieldName,
676                                            ((KualiDocumentFormBase) form).getDocument());
677                    }
678                    else {
679                            return KNSServiceLocator.getBusinessObjectAuthorizationService().canPartiallyUnmaskField(
680                                            GlobalVariables.getUserSession().getPerson(), businessObjClass, fieldName, null);
681                    }
682            }
683    
684            public static boolean canAddNoteAttachment(Document document) {
685                    boolean canViewNoteAttachment = false;
686                    DocumentAuthorizer documentAuthorizer = KNSServiceLocator.getDocumentHelperService().getDocumentAuthorizer(
687                                    document);
688                    canViewNoteAttachment = documentAuthorizer.canAddNoteAttachment(document, null, GlobalVariables
689                                    .getUserSession().getPerson());
690                    return canViewNoteAttachment;
691            }
692    
693            public static boolean canViewNoteAttachment(Document document, String attachmentTypeCode) {
694                    boolean canViewNoteAttachment = false;
695                    DocumentAuthorizer documentAuthorizer = KNSServiceLocator.getDocumentHelperService().getDocumentAuthorizer(
696                                    document);
697                    canViewNoteAttachment = documentAuthorizer.canViewNoteAttachment(document, attachmentTypeCode, GlobalVariables
698                                    .getUserSession().getPerson());
699                    return canViewNoteAttachment;
700            }
701    
702            public static boolean canDeleteNoteAttachment(Document document, String attachmentTypeCode,
703                            String authorUniversalIdentifier) {
704                    boolean canDeleteNoteAttachment = false;
705                    DocumentAuthorizer documentAuthorizer = KNSServiceLocator.getDocumentHelperService().getDocumentAuthorizer(
706                                    document);
707                    canDeleteNoteAttachment = documentAuthorizer.canDeleteNoteAttachment(document, attachmentTypeCode, "false",
708                                    GlobalVariables.getUserSession().getPerson());
709                    if (canDeleteNoteAttachment) {
710                            return canDeleteNoteAttachment;
711                    }
712                    else {
713                            canDeleteNoteAttachment = documentAuthorizer.canDeleteNoteAttachment(document, attachmentTypeCode, "true",
714                                            GlobalVariables.getUserSession().getPerson());
715                            if (canDeleteNoteAttachment
716                                            && !authorUniversalIdentifier.equals(GlobalVariables.getUserSession().getPerson().getPrincipalId())) {
717                                    canDeleteNoteAttachment = false;
718                            }
719                    }
720                    return canDeleteNoteAttachment;
721            }
722    
723            public static void reuseErrorMapFromPreviousRequest(KualiDocumentFormBase kualiDocumentFormBase) {
724                    if (kualiDocumentFormBase.getMessageMapFromPreviousRequest() == null) {
725                            LOG.error("Error map from previous request is null!");
726                            return;
727                    }
728                    MessageMap errorMapFromGlobalVariables = GlobalVariables.getMessageMap();
729                    if (kualiDocumentFormBase.getMessageMapFromPreviousRequest() == errorMapFromGlobalVariables) {
730                            // if we've switched them already, then return early and do nothing
731                            return;
732                    }
733                    if (!errorMapFromGlobalVariables.hasNoErrors()) {
734                            throw new RuntimeException("Cannot replace error map because it is not empty");
735                    }
736                    GlobalVariables.setMessageMap(kualiDocumentFormBase.getMessageMapFromPreviousRequest());
737                    GlobalVariables.getMessageMap().clearErrorPath();
738            }
739    
740            /**
741             * Excapes out HTML to prevent XSS attacks, and replaces the following
742             * strings to allow for a limited set of HTML tags
743             * 
744             * <li>[X] and [/X], where X represents any 1 or 2 letter string may be used
745             * to specify the equivalent tag in HTML (i.e. &lt;X&gt; and &lt;/X&gt;) <li>
746             * [font COLOR], where COLOR represents any valid html color (i.e. color
747             * name or hexcode preceeded by #) will be filtered into &lt;font
748             * color="COLOR"/&gt; <li>[/font] will be filtered into &lt;/font&gt; <li>
749             * [table CLASS], where CLASS gives the style class to use, will be filter
750             * into &lt;table class="CLASS"/&gt; <li>[/table] will be filtered into
751             * &lt;/table&gt; <li>[td CLASS], where CLASS gives the style class to use,
752             * will be filter into &lt;td class="CLASS"/&gt;
753             * 
754             * @param inputString
755             * @return
756             */
757            public static String filterHtmlAndReplaceRiceMarkup(String inputString) {
758                    String outputString = StringEscapeUtils.escapeHtml(inputString);
759                    // string has been escaped of all <, >, and & (and other characters)
760    
761            Map<String, String> findAndReplacePatterns = new LinkedHashMap<String, String>();
762    
763            // now replace our rice custom markup into html
764    
765            // DON'T ALLOW THE SCRIPT TAG OR ARBITRARY IMAGES/URLS/ETC. THROUGH
766    
767            //strip out instances where javascript precedes a URL
768            findAndReplacePatterns.put("\\[a ((javascript|JAVASCRIPT|JavaScript).+)\\]", "");
769            //turn passed a href value into appropriate tag
770            findAndReplacePatterns.put("\\[a (.+)\\]", "<a href=\"$1\">");
771            findAndReplacePatterns.put("\\[/a\\]", "</a>");
772            
773                    // filter any one character tags
774                    findAndReplacePatterns.put("\\[([A-Za-z])\\]", "<$1>");
775                    findAndReplacePatterns.put("\\[/([A-Za-z])\\]", "</$1>");
776                    // filter any two character tags
777                    findAndReplacePatterns.put("\\[([A-Za-z]{2})\\]", "<$1>");
778                    findAndReplacePatterns.put("\\[/([A-Za-z]{2})\\]", "</$1>");
779                    // filter the font tag
780                    findAndReplacePatterns.put("\\[font (#[0-9A-Fa-f]{1,6}|[A-Za-z]+)\\]", "<font color=\"$1\">");
781                    findAndReplacePatterns.put("\\[/font\\]", "</font>");
782                    // filter the table tag
783                    findAndReplacePatterns.put("\\[table\\]", "<table>");
784                    findAndReplacePatterns.put("\\[table ([A-Za-z]+)\\]", "<table class=\"$1\">");
785                    findAndReplacePatterns.put("\\[/table\\]", "</table>");
786                    // fiter td with class
787                    findAndReplacePatterns.put("\\[td ([A-Za-z]+)\\]", "<td class=\"$1\">");
788    
789                    for (String findPattern : findAndReplacePatterns.keySet()) {
790                            Pattern p = Pattern.compile(findPattern);
791                            Matcher m = p.matcher(outputString);
792                            if (m.find()) {
793                                    String replacePattern = findAndReplacePatterns.get(findPattern);
794                                    outputString = m.replaceAll(replacePattern);
795                            }
796                    }
797    
798                    return outputString;
799            }
800    
801        /**
802             * Determines and returns the URL for question button images; looks first
803             * for a property "application.custom.image.url", and if that is missing,
804             * uses the image url returned by getDefaultButtonImageUrl()
805             * 
806             * @param imageName
807             *            the name of the image to find a button for
808             * @return the URL where question button images are located
809             */
810            public static String getButtonImageUrl(String imageName) {
811                    String buttonImageUrl = getKualiConfigurationService().getPropertyValueAsString(
812                    WebUtils.APPLICATION_IMAGE_URL_PROPERTY_PREFIX + "." + imageName);
813                    if (StringUtils.isBlank(buttonImageUrl)) {
814                            buttonImageUrl = getDefaultButtonImageUrl(imageName);
815                    }
816                    return buttonImageUrl;
817            }
818    
819        public static String getAttachmentImageForUrl(String contentType) {
820            String image = getKualiConfigurationService().getPropertyValueAsString(KRADConstants.ATTACHMENT_IMAGE_PREFIX + contentType);
821            if (StringUtils.isEmpty(image)) {
822                return getKualiConfigurationService().getPropertyValueAsString(KRADConstants.ATTACHMENT_IMAGE_DEFAULT);
823            }
824            return image;
825        }
826    
827            /**
828             * Generates a default button image URL, in the form of:
829             * ${kr.externalizable.images.url}buttonsmall_${imageName}.gif
830             * 
831             * @param imageName
832             *            the image name to generate a default button name for
833             * @return the default button image url
834             */
835            public static String getDefaultButtonImageUrl(String imageName) {
836                    return getKualiConfigurationService().getPropertyValueAsString(WebUtils.DEFAULT_IMAGE_URL_PROPERTY_NAME)
837                                    + "buttonsmall_" + imageName + ".gif";
838            }
839    
840        /**
841             * @return an implementation of the KualiConfigurationService
842             */
843            public static ConfigurationService getKualiConfigurationService() {
844                    if (configurationService == null) {
845                            configurationService = KRADServiceLocator.getKualiConfigurationService();
846                    }
847                    return configurationService;
848            }
849            
850        /**
851         * Takes a string an converts the whitespace which would be ignored in an
852         * HTML document into HTML elements so the whitespace is preserved
853         * 
854         * @param startingString The string to preserve whitespace in
855         * @return A string whose whitespace has been converted to HTML elements to preserve the whitespace in an HTML document
856         */
857        public static String preserveWhitespace(String startingString) {
858            String convertedString = startingString.replaceAll("\n", "<br />");
859            convertedString = convertedString.replaceAll("  ", "&nbsp;&nbsp;").replaceAll("(&nbsp; | &nbsp;)", "&nbsp;&nbsp;");
860            return convertedString;
861        }
862        
863        public static String getKimGroupDisplayName(String groupId) {
864            if(StringUtils.isBlank(groupId)) {
865                    throw new IllegalArgumentException("Group ID must have a value");
866            }
867            return KimApiServiceLocator.getGroupService().getGroup(groupId).getName();
868        }
869        
870        public static String getPrincipalDisplayName(String principalId) {
871            if(StringUtils.isBlank(principalId)) {
872                    throw new IllegalArgumentException("Principal ID must have a value");
873            }
874            return KimApiServiceLocator.getIdentityService().getDefaultNamesForPrincipalId(principalId).getDefaultName().getCompositeName();
875        }
876    
877        /**
878         * Takes an {@link org.kuali.rice.kew.api.action.ActionRequest} with a recipient type of
879         * {@link org.kuali.rice.kew.api.action.RecipientType#ROLE} and returns the display name for the role.
880         *
881         * @param actionRequest the action request
882         * @return the display name for the role
883         * @throws IllegalArgumentException if the action request is null, or the recipient type is not ROLE
884         */
885        public static String getRoleDisplayName(ActionRequest actionRequest) {
886            String result;
887    
888            if(actionRequest == null) {
889                throw new IllegalArgumentException("actionRequest must be non-null");
890            }
891            if (RecipientType.ROLE != actionRequest.getRecipientType()) {
892                throw new IllegalArgumentException("actionRequest recipient must be a Role");
893            }
894    
895            Role role = KimApiServiceLocator.getRoleService().getRole(actionRequest.getRoleName());
896    
897            if (role != null) {
898                result = role.getName();
899            } else if (!StringUtils.isBlank(actionRequest.getQualifiedRoleNameLabel())) {
900                result = actionRequest.getQualifiedRoleNameLabel();
901            } else {
902                result = actionRequest.getRoleName();
903            }
904    
905            return result;
906        }
907    
908        /**
909         * Returns an absolute URL which is a combination of a base part plus path,
910         * or in the case that the path is already an absolute URL, the path alone
911         * @param base the url base path
912         * @param path the path to append to base
913         * @return an absolute URL representing the combination of base+path, or path alone if it is already absolute
914         */
915        public static String toAbsoluteURL(String base, String path) {
916            boolean abs = false;
917            if (StringUtils.isBlank(path)) {
918                path = "";
919            } else {
920                for (String scheme: SCHEMES) {
921                    if (path.startsWith(scheme)) {
922                        abs = true;
923                        break;
924                    }
925                }
926            }
927            if (abs) {
928                return path;
929            }
930            return base + path;
931        }
932    
933    }