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