001/**
002 * Copyright 2005-2016 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 */
016package org.kuali.rice.kns.util;
017
018import org.apache.commons.lang.StringEscapeUtils;
019import org.apache.commons.lang.StringUtils;
020import org.apache.log4j.Level;
021import org.apache.log4j.Logger;
022import org.apache.struts.Globals;
023import org.apache.struts.action.ActionForm;
024import org.apache.struts.action.ActionMapping;
025import org.apache.struts.action.ActionServletWrapper;
026import org.apache.struts.upload.CommonsMultipartRequestHandler;
027import org.apache.struts.upload.FormFile;
028import org.apache.struts.upload.MultipartRequestHandler;
029import org.apache.struts.upload.MultipartRequestWrapper;
030import org.kuali.rice.core.api.CoreApiServiceLocator;
031import org.kuali.rice.core.api.config.property.ConfigContext;
032import org.kuali.rice.core.api.config.property.ConfigurationService;
033import org.kuali.rice.core.api.util.RiceKeyConstants;
034import org.kuali.rice.kew.api.action.ActionRequest;
035import org.kuali.rice.kew.api.action.RecipientType;
036import org.kuali.rice.kim.api.role.Role;
037import org.kuali.rice.kim.api.services.KimApiServiceLocator;
038import org.kuali.rice.kns.datadictionary.KNSDocumentEntry;
039import org.kuali.rice.kns.datadictionary.MaintenanceDocumentEntry;
040import org.kuali.rice.kns.document.authorization.DocumentAuthorizer;
041import org.kuali.rice.kns.service.KNSServiceLocator;
042import org.kuali.rice.kns.web.struts.action.KualiMultipartRequestHandler;
043import org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase;
044import org.kuali.rice.kns.web.struts.form.KualiForm;
045import org.kuali.rice.kns.web.struts.form.KualiMaintenanceForm;
046import org.kuali.rice.kns.web.struts.form.pojo.PojoFormBase;
047import org.kuali.rice.kns.web.ui.Field;
048import org.kuali.rice.kns.web.ui.Row;
049import org.kuali.rice.kns.web.ui.Section;
050import org.kuali.rice.krad.datadictionary.AttributeDefinition;
051import org.kuali.rice.krad.datadictionary.AttributeSecurity;
052import org.kuali.rice.krad.datadictionary.DataDictionary;
053import org.kuali.rice.krad.datadictionary.DataDictionaryEntryBase;
054import org.kuali.rice.krad.datadictionary.mask.MaskFormatter;
055import org.kuali.rice.krad.document.Document;
056import org.kuali.rice.krad.exception.ValidationException;
057import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
058import org.kuali.rice.krad.util.GlobalVariables;
059import org.kuali.rice.krad.util.KRADConstants;
060import org.kuali.rice.krad.util.MessageMap;
061import org.kuali.rice.krad.util.ObjectUtils;
062
063import javax.servlet.ServletException;
064import javax.servlet.http.HttpServletRequest;
065import javax.servlet.http.HttpServletResponse;
066import javax.servlet.http.HttpSession;
067import javax.servlet.jsp.PageContext;
068import java.io.ByteArrayOutputStream;
069import java.io.IOException;
070import java.io.InputStream;
071import java.io.OutputStream;
072import java.net.URI;
073import java.net.URISyntaxException;
074import java.util.Arrays;
075import java.util.Collection;
076import java.util.Enumeration;
077import java.util.HashMap;
078import java.util.Hashtable;
079import java.util.Iterator;
080import java.util.LinkedHashMap;
081import java.util.List;
082import java.util.Map;
083import java.util.Set;
084import java.util.regex.Matcher;
085import java.util.regex.Pattern;
086
087/**
088 * General helper methods for handling requests.
089 */
090public 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        // If there are quotes in the name, we should replace them to avoid issues.
315        // The filename will be wrapped with quotes below when it is set in the header
316        String updateFileName;
317        if(fileName.contains("\"")) {
318            updateFileName = fileName.replaceAll("\"", "");
319        } else {
320            updateFileName =  fileName;
321        }
322
323                // set response
324                response.setContentType(contentType);
325        response.setHeader("Content-disposition", "attachment; filename=\"" + updateFileName + "\"");
326        response.setHeader("Expires", "0");
327                response.setHeader("Cache-Control", "must-revalidate, post-check=0, pre-check=0");
328                response.setHeader("Pragma", "public");
329                response.setContentLength(byteArrayOutputStream.size());
330
331                // write to output
332                OutputStream outputStream = response.getOutputStream();
333                byteArrayOutputStream.writeTo(response.getOutputStream());
334                outputStream.flush();
335                outputStream.close();
336        }
337
338        /**
339         * A file that is not of type text/plain or text/html can be output through
340         * the response using this method.
341         * 
342         * @param response
343         * @param contentType
344         * @param inStream
345         * @param fileName
346         */
347        public static void saveMimeInputStreamAsFile(HttpServletResponse response, String contentType,
348                        InputStream inStream, String fileName, int fileSize) throws IOException {
349
350        // If there are quotes in the name, we should replace them to avoid issues.
351        // The filename will be wrapped with quotes below when it is set in the header
352        String updateFileName;
353        if(fileName.contains("\"")) {
354            updateFileName = fileName.replaceAll("\"", "");
355        } else {
356            updateFileName =  fileName;
357        }
358
359                // set response
360                response.setContentType(contentType);
361        response.setHeader("Content-disposition", "attachment; filename=\"" + updateFileName + "\"");
362        response.setHeader("Expires", "0");
363                response.setHeader("Cache-Control", "must-revalidate, post-check=0, pre-check=0");
364                response.setHeader("Pragma", "public");
365                response.setContentLength(fileSize);
366
367                // write to output
368                OutputStream out = response.getOutputStream();
369                while (inStream.available() > 0) {
370                        out.write(inStream.read());
371                }
372                out.flush();
373        }
374
375        /**
376         * JSTL function to return the tab state of the tab from the form.
377         * 
378         * @param form
379         * @param tabKey
380         * @return
381         */
382        public static String getTabState(KualiForm form, String tabKey) {
383                return form.getTabState(tabKey);
384        }
385
386        public static void incrementTabIndex(KualiForm form, String tabKey) {
387                form.incrementTabIndex();
388        }
389
390    /**
391     * Attempts to reopen sub tabs which would have been closed for inactive records
392     *
393     * @param sections the list of Sections whose rows and fields to set the open tab state on
394     * @param tabStates the map of tabKey->tabState.  This map will be modified to set entries to "OPEN"
395     * @param collectionName the name of the collection reopening
396     */
397    public static void reopenInactiveRecords(List<Section> sections, Map<String, String> tabStates, String collectionName) {
398        for (Section section : sections) {
399            for (Row row: section.getRows()) {
400                for (Field field : row.getFields()) {
401                    if (field != null) {
402                        if (Field.CONTAINER.equals(field.getFieldType()) && StringUtils.startsWith(field.getContainerName(), collectionName)) {
403                            final String tabKey = WebUtils.generateTabKey(FieldUtils.generateCollectionSubTabName(field));
404                            tabStates.put(tabKey, KualiForm.TabState.OPEN.name());
405                        }
406                    }
407                }
408            }
409        }
410    }
411
412        /**
413         * Generates a String from the title that can be used as a Map key.
414         * 
415         * @param tabTitle
416         * @return
417         */
418        public static String generateTabKey(String tabTitle) {
419                String key = "";
420                if (!StringUtils.isBlank(tabTitle)) {
421                        key = tabTitle.replaceAll("\\W", "");
422                        // if (key.length() > 25) {
423                        // key = key.substring(0, 24);
424                        // }
425                }
426
427                return key;
428        }
429
430        public static void getMultipartParameters(HttpServletRequest request, ActionServletWrapper servletWrapper,
431                        ActionForm form, ActionMapping mapping) {
432                Map params = new HashMap();
433
434                // Get the ActionServletWrapper from the form bean
435                // ActionServletWrapper servletWrapper = getServletWrapper();
436
437                try {
438                        CommonsMultipartRequestHandler multipartHandler = new CommonsMultipartRequestHandler();
439                        if (multipartHandler != null) {
440                                // Set servlet and mapping info
441                                if (servletWrapper != null) {
442                                        // from pojoformbase
443                                        // servlet only affects tempdir on local disk
444                                        servletWrapper.setServletFor(multipartHandler);
445                                }
446                                multipartHandler.setMapping((ActionMapping) request.getAttribute(Globals.MAPPING_KEY));
447                                // Initialize multipart request class handler
448                                multipartHandler.handleRequest(request);
449
450                                Collection<FormFile> files = multipartHandler.getFileElements().values();
451                                Enumeration keys = multipartHandler.getFileElements().keys();
452
453                                while (keys.hasMoreElements()) {
454                                        Object key = keys.nextElement();
455                                        FormFile file = (FormFile) multipartHandler.getFileElements().get(key);
456                                        long maxSize = WebUtils.getMaxUploadSize(form);
457                                        if (LOG.isDebugEnabled()) {
458                                                LOG.debug(file.getFileSize());
459                                        }
460                                        if (maxSize > 0 && Long.parseLong(file.getFileSize() + "") > maxSize) {
461
462                                                GlobalVariables.getMessageMap().putError(key.toString(),
463                                                                RiceKeyConstants.ERROR_UPLOADFILE_SIZE,
464                                                                new String[] { file.getFileName(), Long.toString(maxSize) });
465
466                                        }
467                                }
468
469                                // get file elements for kualirequestprocessor
470                                if (servletWrapper == null) {
471                                        request.setAttribute(KRADConstants.UPLOADED_FILE_REQUEST_ATTRIBUTE_KEY,
472                                                        getFileParametersForMultipartRequest(request, multipartHandler));
473                                }
474                        }
475                }
476                catch (ServletException e) {
477                        throw new ValidationException("unable to handle multipart request " + e.getMessage(), e);
478                }
479        }
480
481        public static long getMaxUploadSize(ActionForm form) {
482                long max = 0L;
483                KualiMultipartRequestHandler multipartHandler = new KualiMultipartRequestHandler();
484                if (form instanceof PojoFormBase) {
485                        max = multipartHandler.calculateMaxUploadSizeToMaxOfList(((PojoFormBase) form).getMaxUploadSizes());
486                }
487                if (LOG.isDebugEnabled()) {
488                        LOG.debug("Max File Upload Size: " + max);
489                }
490                return max;
491        }
492
493        private static Map getFileParametersForMultipartRequest(HttpServletRequest request,
494                        MultipartRequestHandler multipartHandler) {
495                Map parameters = new HashMap();
496                Hashtable elements = multipartHandler.getFileElements();
497                Enumeration e = elements.keys();
498                while (e.hasMoreElements()) {
499                        String key = (String) e.nextElement();
500                        parameters.put(key, elements.get(key));
501                }
502
503                if (request instanceof MultipartRequestWrapper) {
504                        request = (HttpServletRequest) ((MultipartRequestWrapper) request).getRequest();
505                        e = request.getParameterNames();
506                        while (e.hasMoreElements()) {
507                                String key = (String) e.nextElement();
508                                parameters.put(key, request.getParameterValues(key));
509                        }
510                }
511                else {
512                        LOG.debug("Gathering multipart parameters for unwrapped request");
513                }
514                return parameters;
515        }
516
517        // end multipart
518
519        public static void registerEditableProperty(PojoFormBase form, String editablePropertyName) {
520                form.registerEditableProperty(editablePropertyName);
521        }
522
523        public static boolean isDocumentSession(Document document, PojoFormBase docForm) {
524                boolean sessionDoc = document instanceof org.kuali.rice.krad.document.SessionDocument;
525                boolean dataDictionarySessionDoc = false;
526                if (!sessionDoc) {
527                        DataDictionary dataDictionary = KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary();
528                        if (docForm instanceof KualiMaintenanceForm) {
529                                KualiMaintenanceForm maintenanceForm = (KualiMaintenanceForm) docForm;
530                                if (dataDictionary != null) {
531                                        if (maintenanceForm.getDocTypeName() != null) {
532                        MaintenanceDocumentEntry maintenanceDocumentEntry = (MaintenanceDocumentEntry) dataDictionary.getDocumentEntry(maintenanceForm.getDocTypeName());
533                                                dataDictionarySessionDoc = maintenanceDocumentEntry.isSessionDocument();
534                                        }
535                                }
536                        }
537                        else {
538                                if (document != null && dataDictionary != null) {
539                                        KNSDocumentEntry documentEntry = (KNSDocumentEntry) dataDictionary.getDocumentEntry(document.getClass().getName());
540                                        dataDictionarySessionDoc = documentEntry.isSessionDocument();
541                                }
542                        }
543                }
544                return sessionDoc || dataDictionarySessionDoc;
545        }
546
547        public static boolean isFormSessionDocument(PojoFormBase form) {
548                Document document = null;
549                if (KualiDocumentFormBase.class.isAssignableFrom(form.getClass())) {
550                        KualiDocumentFormBase docForm = (KualiDocumentFormBase) form;
551                        document = docForm.getDocument();
552                }
553                return isDocumentSession(document, form);
554        }
555
556        public static String KEY_KUALI_FORM_IN_SESSION = "KualiForm";
557
558        public static ActionForm getKualiForm(PageContext pageContext) {
559                return getKualiForm((HttpServletRequest) pageContext.getRequest());
560        }
561
562        public static ActionForm getKualiForm(HttpServletRequest request) {
563                if (request.getAttribute(KEY_KUALI_FORM_IN_SESSION) != null) {
564                        return (ActionForm) request.getAttribute(KEY_KUALI_FORM_IN_SESSION);
565                }
566                else {
567                        final HttpSession session = request.getSession(false);
568                        return session != null ? (ActionForm) session.getAttribute(KEY_KUALI_FORM_IN_SESSION) : null;
569                }
570        }
571
572        public static boolean isPropertyEditable(Set<String> editableProperties, String propertyName) {
573                if (LOG.isDebugEnabled()) {
574                        LOG.debug("isPropertyEditable(" + propertyName + ")");
575                }
576
577                boolean returnVal = editableProperties == null
578                                || editableProperties.contains(propertyName)
579                                || (getIndexOfCoordinateExtension(propertyName) == -1 ? false : editableProperties
580                                                .contains(propertyName.substring(0, getIndexOfCoordinateExtension(propertyName))));
581                if (!returnVal) {
582                        if (LOG.isDebugEnabled()) {
583                                LOG.debug("isPropertyEditable(" + propertyName + ") == false / editableProperties: "
584                                                + editableProperties);
585                        }
586                }
587                return returnVal;
588        }
589
590        public static boolean endsWithCoordinates(String parameter) {
591                return parameter.endsWith(WebUtils.IMAGE_COORDINATE_CLICKED_X_EXTENSION)
592                                || parameter.endsWith(WebUtils.IMAGE_COORDINATE_CLICKED_Y_EXTENSION);
593        }
594
595        public static int getIndexOfCoordinateExtension(String parameter) {
596                int indexOfCoordinateExtension = parameter.lastIndexOf(WebUtils.IMAGE_COORDINATE_CLICKED_X_EXTENSION);
597        if (indexOfCoordinateExtension == -1) {
598            indexOfCoordinateExtension = parameter.lastIndexOf(WebUtils.IMAGE_COORDINATE_CLICKED_Y_EXTENSION);
599        }
600                return indexOfCoordinateExtension;
601        }
602
603    public static boolean isInquiryHiddenField(String className, String fieldName, Object formObject, String propertyName) {
604        boolean isHidden = false;
605        String hiddenInquiryFields = getKualiConfigurationService().getPropertyValueAsString(className + ".hidden");
606        if (StringUtils.isEmpty(hiddenInquiryFields)) {
607                return isHidden;
608        }
609        List hiddenFields = Arrays.asList(hiddenInquiryFields.replaceAll(" ", "").split(","));
610        if (hiddenFields.contains(fieldName.trim())) {
611                isHidden = true;
612        }
613        return isHidden;
614    }
615
616    public static boolean isHiddenKimObjectType(String type, String configParameter) {
617        boolean hideType = false;
618        String hiddenTypes = getKualiConfigurationService().getPropertyValueAsString(configParameter);
619        if (StringUtils.isEmpty(hiddenTypes)) {
620                return hideType;
621        }
622        List hiddenTypeValues = Arrays.asList(hiddenTypes.replaceAll(" ", "").split(","));
623        if (hiddenTypeValues.contains(type.trim())) {
624                hideType = true;
625        }
626        return hideType;
627    }
628
629        public static String getFullyMaskedValue(String className, String fieldName, Object formObject, 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.isMask()) {
639                        MaskFormatter maskFormatter = attributeSecurity.getMaskFormatter();
640                        displayMaskValue = maskFormatter.maskValue(propertyValue);
641
642                }
643                return displayMaskValue;
644        }
645
646        public static String getPartiallyMaskedValue(String className, String fieldName, Object formObject,
647                        String propertyName) {
648                String displayMaskValue = null;
649                Object propertyValue = ObjectUtils.getPropertyValue(formObject, propertyName);
650
651                DataDictionaryEntryBase entry = (DataDictionaryEntryBase) KRADServiceLocatorWeb.getDataDictionaryService()
652                                .getDataDictionary().getDictionaryObjectEntry(className);
653                AttributeDefinition a = entry.getAttributeDefinition(fieldName);
654
655                AttributeSecurity attributeSecurity = a.getAttributeSecurity();
656                if (attributeSecurity != null && attributeSecurity.isPartialMask()) {
657                        MaskFormatter partialMaskFormatter = attributeSecurity.getPartialMaskFormatter();
658                        displayMaskValue = partialMaskFormatter.maskValue(propertyValue);
659
660                }
661                return displayMaskValue;
662        }
663
664        public static boolean canFullyUnmaskField(String businessObjectClassName, String fieldName, KualiForm form) {
665                Class businessObjClass = null;
666                try {
667                        businessObjClass = Class.forName(businessObjectClassName);
668                }
669                catch (Exception e) {
670                        throw new RuntimeException("Unable to resolve class name: " + businessObjectClassName);
671                }
672                if (form instanceof KualiDocumentFormBase) {
673                        return KNSServiceLocator.getBusinessObjectAuthorizationService().canFullyUnmaskField(
674                                        GlobalVariables.getUserSession().getPerson(), businessObjClass, fieldName,
675                                        ((KualiDocumentFormBase) form).getDocument());
676                }
677                else {
678                        return KNSServiceLocator.getBusinessObjectAuthorizationService().canFullyUnmaskField(
679                                        GlobalVariables.getUserSession().getPerson(), businessObjClass, fieldName, null);
680                }
681        }
682
683        public static boolean canPartiallyUnmaskField(String businessObjectClassName, String fieldName, KualiForm form) {
684                Class businessObjClass = null;
685                try {
686                        businessObjClass = Class.forName(businessObjectClassName);
687                }
688                catch (Exception e) {
689                        throw new RuntimeException("Unable to resolve class name: " + businessObjectClassName);
690                }
691                if (form instanceof KualiDocumentFormBase) {
692                        return KNSServiceLocator.getBusinessObjectAuthorizationService().canPartiallyUnmaskField(
693                                        GlobalVariables.getUserSession().getPerson(), businessObjClass, fieldName,
694                                        ((KualiDocumentFormBase) form).getDocument());
695                }
696                else {
697                        return KNSServiceLocator.getBusinessObjectAuthorizationService().canPartiallyUnmaskField(
698                                        GlobalVariables.getUserSession().getPerson(), businessObjClass, fieldName, null);
699                }
700        }
701
702        public static boolean canAddNoteAttachment(Document document) {
703                boolean canViewNoteAttachment = false;
704                DocumentAuthorizer documentAuthorizer = KNSServiceLocator.getDocumentHelperService().getDocumentAuthorizer(
705                                document);
706                canViewNoteAttachment = documentAuthorizer.canAddNoteAttachment(document, null, GlobalVariables
707                                .getUserSession().getPerson());
708                return canViewNoteAttachment;
709        }
710
711        public static boolean canViewNoteAttachment(Document document, String attachmentTypeCode) {
712                boolean canViewNoteAttachment = false;
713                DocumentAuthorizer documentAuthorizer = KNSServiceLocator.getDocumentHelperService().getDocumentAuthorizer(
714                                document);
715                canViewNoteAttachment = documentAuthorizer.canViewNoteAttachment(document, attachmentTypeCode, GlobalVariables
716                                .getUserSession().getPerson());
717                return canViewNoteAttachment;
718        }
719
720        public static boolean canDeleteNoteAttachment(Document document, String attachmentTypeCode,
721                        String authorUniversalIdentifier) {
722                boolean canDeleteNoteAttachment = false;
723                DocumentAuthorizer documentAuthorizer = KNSServiceLocator.getDocumentHelperService().getDocumentAuthorizer(
724                                document);
725                canDeleteNoteAttachment = documentAuthorizer.canDeleteNoteAttachment(document, attachmentTypeCode, "false",
726                                GlobalVariables.getUserSession().getPerson());
727                if (canDeleteNoteAttachment) {
728                        return canDeleteNoteAttachment;
729                }
730                else {
731                        canDeleteNoteAttachment = documentAuthorizer.canDeleteNoteAttachment(document, attachmentTypeCode, "true",
732                                        GlobalVariables.getUserSession().getPerson());
733                        if (canDeleteNoteAttachment
734                                        && !authorUniversalIdentifier.equals(GlobalVariables.getUserSession().getPerson().getPrincipalId())) {
735                                canDeleteNoteAttachment = false;
736                        }
737                }
738                return canDeleteNoteAttachment;
739        }
740
741        public static void reuseErrorMapFromPreviousRequest(KualiDocumentFormBase kualiDocumentFormBase) {
742                if (kualiDocumentFormBase.getMessageMapFromPreviousRequest() == null) {
743                        LOG.error("Error map from previous request is null!");
744                        return;
745                }
746                MessageMap errorMapFromGlobalVariables = GlobalVariables.getMessageMap();
747                if (kualiDocumentFormBase.getMessageMapFromPreviousRequest() == errorMapFromGlobalVariables) {
748                        // if we've switched them already, then return early and do nothing
749                        return;
750                }
751                if (!errorMapFromGlobalVariables.hasNoErrors()) {
752                        throw new RuntimeException("Cannot replace error map because it is not empty");
753                }
754                GlobalVariables.setMessageMap(kualiDocumentFormBase.getMessageMapFromPreviousRequest());
755                GlobalVariables.getMessageMap().clearErrorPath();
756        }
757
758        /**
759         * Excapes out HTML to prevent XSS attacks, and replaces the following
760         * strings to allow for a limited set of HTML tags
761         * 
762         * <li>[X] and [/X], where X represents any 1 or 2 letter string may be used
763         * to specify the equivalent tag in HTML (i.e. &lt;X&gt; and &lt;/X&gt;) <li>
764         * [font COLOR], where COLOR represents any valid html color (i.e. color
765         * name or hexcode preceeded by #) will be filtered into &lt;font
766         * color="COLOR"/&gt; <li>[/font] will be filtered into &lt;/font&gt; <li>
767         * [table CLASS], where CLASS gives the style class to use, will be filter
768         * into &lt;table class="CLASS"/&gt; <li>[/table] will be filtered into
769         * &lt;/table&gt; <li>[td CLASS], where CLASS gives the style class to use,
770         * will be filter into &lt;td class="CLASS"/&gt;
771         * 
772         * @param inputString
773         * @return
774         */
775        public static String filterHtmlAndReplaceRiceMarkup(String inputString) {
776                String outputString = StringEscapeUtils.escapeHtml(inputString);
777                // string has been escaped of all <, >, and & (and other characters)
778
779        Map<String, String> findAndReplacePatterns = new LinkedHashMap<String, String>();
780
781        // now replace our rice custom markup into html
782
783        // DON'T ALLOW THE SCRIPT TAG OR ARBITRARY IMAGES/URLS/ETC. THROUGH
784
785        //strip out instances where javascript precedes a URL
786        findAndReplacePatterns.put("\\[a ((javascript|JAVASCRIPT|JavaScript).+)\\]", "");
787        //turn passed a href value into appropriate tag
788        findAndReplacePatterns.put("\\[a (.+)\\]", "<a href=\"$1\">");
789        findAndReplacePatterns.put("\\[/a\\]", "</a>");
790        
791                // filter any one character tags
792                findAndReplacePatterns.put("\\[([A-Za-z])\\]", "<$1>");
793                findAndReplacePatterns.put("\\[/([A-Za-z])\\]", "</$1>");
794                // filter any two character tags
795                findAndReplacePatterns.put("\\[([A-Za-z]{2})\\]", "<$1>");
796                findAndReplacePatterns.put("\\[/([A-Za-z]{2})\\]", "</$1>");
797                // filter the font tag
798                findAndReplacePatterns.put("\\[font (#[0-9A-Fa-f]{1,6}|[A-Za-z]+)\\]", "<font color=\"$1\">");
799                findAndReplacePatterns.put("\\[/font\\]", "</font>");
800                // filter the table tag
801                findAndReplacePatterns.put("\\[table\\]", "<table>");
802                findAndReplacePatterns.put("\\[table ([A-Za-z]+)\\]", "<table class=\"$1\">");
803                findAndReplacePatterns.put("\\[/table\\]", "</table>");
804                // fiter td with class
805                findAndReplacePatterns.put("\\[td ([A-Za-z]+)\\]", "<td class=\"$1\">");
806
807                for (String findPattern : findAndReplacePatterns.keySet()) {
808                        Pattern p = Pattern.compile(findPattern);
809                        Matcher m = p.matcher(outputString);
810                        if (m.find()) {
811                                String replacePattern = findAndReplacePatterns.get(findPattern);
812                                outputString = m.replaceAll(replacePattern);
813                        }
814                }
815
816                return outputString;
817        }
818
819    /**
820         * Determines and returns the URL for question button images; looks first
821         * for a property "application.custom.image.url", and if that is missing,
822         * uses the image url returned by getDefaultButtonImageUrl()
823         * 
824         * @param imageName
825         *            the name of the image to find a button for
826         * @return the URL where question button images are located
827         */
828        public static String getButtonImageUrl(String imageName) {
829                String buttonImageUrl = getKualiConfigurationService().getPropertyValueAsString(
830                WebUtils.APPLICATION_IMAGE_URL_PROPERTY_PREFIX + "." + imageName);
831                if (StringUtils.isBlank(buttonImageUrl)) {
832                        buttonImageUrl = getDefaultButtonImageUrl(imageName);
833                }
834                return buttonImageUrl;
835        }
836
837    public static String getAttachmentImageForUrl(String contentType) {
838        String image = getKualiConfigurationService().getPropertyValueAsString(KRADConstants.ATTACHMENT_IMAGE_PREFIX + contentType);
839        if (StringUtils.isEmpty(image)) {
840            return getKualiConfigurationService().getPropertyValueAsString(KRADConstants.ATTACHMENT_IMAGE_DEFAULT);
841        }
842        return image;
843    }
844
845        /**
846         * Generates a default button image URL, in the form of:
847         * ${kr.externalizable.images.url}buttonsmall_${imageName}.gif
848         * 
849         * @param imageName
850         *            the image name to generate a default button name for
851         * @return the default button image url
852         */
853        public static String getDefaultButtonImageUrl(String imageName) {
854                return getKualiConfigurationService().getPropertyValueAsString(WebUtils.DEFAULT_IMAGE_URL_PROPERTY_NAME)
855                                + "buttonsmall_" + imageName + ".gif";
856        }
857
858    /**
859         * @return an implementation of the KualiConfigurationService
860         */
861        public static ConfigurationService getKualiConfigurationService() {
862                if (configurationService == null) {
863                        configurationService = CoreApiServiceLocator.getKualiConfigurationService();
864                }
865                return configurationService;
866        }
867        
868    /**
869     * Takes a string an converts the whitespace which would be ignored in an
870     * HTML document into HTML elements so the whitespace is preserved
871     * 
872     * @param startingString The string to preserve whitespace in
873     * @return A string whose whitespace has been converted to HTML elements to preserve the whitespace in an HTML document
874     */
875    public static String preserveWhitespace(String startingString) {
876        String convertedString = startingString.replaceAll("\n", "<br />");
877        convertedString = convertedString.replaceAll("  ", "&nbsp;&nbsp;").replaceAll("(&nbsp; | &nbsp;)", "&nbsp;&nbsp;");
878        return convertedString;
879    }
880    
881    public static String getKimGroupDisplayName(String groupId) {
882        if(StringUtils.isBlank(groupId)) {
883                throw new IllegalArgumentException("Group ID must have a value");
884        }
885        return KimApiServiceLocator.getGroupService().getGroup(groupId).getName();
886    }
887    
888    public static String getPrincipalDisplayName(String principalId) {
889        if(StringUtils.isBlank(principalId)) {
890                throw new IllegalArgumentException("Principal ID must have a value");
891        }
892        if (KimApiServiceLocator.getIdentityService().getDefaultNamesForPrincipalId(principalId) == null){
893            return "";
894        }
895        else {
896            return KimApiServiceLocator.getIdentityService().getDefaultNamesForPrincipalId(principalId).getDefaultName().getCompositeName();
897        }
898    }
899
900    /**
901     * Takes an {@link org.kuali.rice.kew.api.action.ActionRequest} with a recipient type of
902     * {@link org.kuali.rice.kew.api.action.RecipientType#ROLE} and returns the display name for the role.
903     *
904     * @param actionRequest the action request
905     * @return the display name for the role
906     * @throws IllegalArgumentException if the action request is null, or the recipient type is not ROLE
907     */
908    public static String getRoleDisplayName(ActionRequest actionRequest) {
909        String result;
910
911        if(actionRequest == null) {
912            throw new IllegalArgumentException("actionRequest must be non-null");
913        }
914        if (RecipientType.ROLE != actionRequest.getRecipientType()) {
915            throw new IllegalArgumentException("actionRequest recipient must be a Role");
916        }
917
918        Role role = KimApiServiceLocator.getRoleService().getRole(actionRequest.getRoleName());
919
920        if (role != null) {
921            result = role.getName();
922        } else if (!StringUtils.isBlank(actionRequest.getQualifiedRoleNameLabel())) {
923            result = actionRequest.getQualifiedRoleNameLabel();
924        } else {
925            result = actionRequest.getRoleName();
926        }
927
928        return result;
929    }
930
931    /**
932     * Returns an absolute URL which is a combination of a base part plus path,
933     * or in the case that the path is already an absolute URL, the path alone
934     * @param base the url base path
935     * @param path the path to append to base
936     * @return an absolute URL representing the combination of base+path, or path alone if it is already absolute
937     */
938    public static String toAbsoluteURL(String base, String path) {
939        boolean abs = false;
940        if (StringUtils.isBlank(path)) {
941            path = "";
942        } else {
943            for (String scheme: SCHEMES) {
944                if (path.startsWith(scheme)) {
945                    abs = true;
946                    break;
947                }
948            }
949        }
950        if (abs) {
951            return path;
952        }
953        return base + path;
954    }
955
956        public static String sanitizeBackLocation(String backLocation) {
957                if(StringUtils.isBlank(backLocation)) {
958                        return backLocation;
959                }
960                // if it's a relative URL then we should be good regardless
961                try {
962                        URI uri = new URI(backLocation);
963                        if (!uri.isAbsolute()) {
964                                return backLocation;
965                        }
966                } catch (URISyntaxException e) {
967                        // move on to the other checks if it doesn't parse up as a URI for some reason
968                }
969                Pattern pattern = Pattern.compile(ConfigContext.getCurrentContextConfig().getProperty(KRADConstants.BACK_LOCATION_ALLOWED_REGEX));
970                if(pattern.matcher(backLocation).matches()) {
971                        return backLocation;
972                } else {
973                        return ConfigContext.getCurrentContextConfig().getProperty(KRADConstants.BACK_LOCATION_DEFAULT_URL);
974                }
975        }
976
977}