Source Code Cross Referenced for PortletStateManager.java in  » Portal » uPortal_rel-2-6-1-GA » org » jasig » portal » container » services » information » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » Portal » uPortal_rel 2 6 1 GA » org.jasig.portal.container.services.information 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


001:        /* Copyright 2004 The JA-SIG Collaborative.  All rights reserved.
002:         *  See license distributed with this file and
003:         *  available online at http://www.uportal.org/license.html
004:         */
005:
006:        package org.jasig.portal.container.services.information;
007:
008:        import java.io.UnsupportedEncodingException;
009:        import java.net.URLDecoder;
010:        import java.net.URLEncoder;
011:        import java.util.HashMap;
012:        import java.util.Iterator;
013:        import java.util.Map;
014:
015:        import javax.portlet.PortletMode;
016:        import javax.portlet.WindowState;
017:        import javax.servlet.ServletRequest;
018:        import javax.servlet.http.HttpServletRequest;
019:        import javax.servlet.http.HttpServletRequestWrapper;
020:        import javax.servlet.http.HttpSession;
021:
022:        import org.apache.commons.logging.Log;
023:        import org.apache.commons.logging.LogFactory;
024:        import org.apache.pluto.om.window.PortletWindow;
025:        import org.jasig.portal.ChannelManager;
026:        import org.jasig.portal.ChannelRuntimeData;
027:        import org.jasig.portal.PortalException;
028:        import org.jasig.portal.UPFileSpec;
029:        import org.jasig.portal.UserInstance;
030:        import org.jasig.portal.container.om.window.PortletWindowImpl;
031:        import org.jasig.portal.layout.IUserLayout;
032:
033:        /**
034:         * The PortletStateManager implementation.
035:         * Analyzes the incoming request parameters for the given PortletWindow, 
036:         * changes the window states/portlet modes, stores them in the static hash maps, 
037:         * builds a portlet URL based on the changed modes/states and 
038:         * portlet render parameters for the current PortletWindow.
039:         * 
040:         * @author Michael Ivanov, mvi@immagic.com
041:         * @version $Revision: 42278 $
042:         */
043:        public class PortletStateManager {
044:            private static final Log LOG = LogFactory
045:                    .getLog(PortletStateManager.class);
046:
047:            public static final String UP_PARAM_PREFIX = "uP_";
048:
049:            // The portlet control parameter names
050:            public static final String ACTION = UP_PARAM_PREFIX
051:                    + "portlet_action";
052:            public static final String UP_ROOT = UP_PARAM_PREFIX + "root";
053:            public static final String UP_TCATTR = UP_PARAM_PREFIX + "tcattr";
054:            public static final String UP_HELP_TARGET = UP_PARAM_PREFIX
055:                    + "help_target";
056:            public static final String UP_EDIT_TARGET = UP_PARAM_PREFIX
057:                    + "edit_target";
058:            public static final String UP_VIEW_TARGET = UP_PARAM_PREFIX
059:                    + "view_target";
060:            public static final String UP_WINDOW_STATE = UP_PARAM_PREFIX
061:                    + "window_state";
062:            public static final String MIN_CHAN_ID = "minimized_channelId";
063:
064:            private static final String MINIMIZED = "minimized";
065:            private static final String ROOT = IUserLayout.ROOT_NODE_NAME;
066:
067:            private final PortletWindowImpl windowOfAction;
068:
069:            //** Fields for URL generation
070:            // Indicates the current action
071:            private boolean isAction;
072:            // Indicates the action of the next request
073:            private boolean nextAction;
074:            private PortletMode nextMode;
075:            private WindowState nextState;
076:
077:            private final Map params = new HashMap();
078:
079:            /**
080:             * Creates a new PortletStateManager instance which can be used for
081:             * generating a URL for the specified PortletWindow.
082:             * 
083:             * @param window The PortletWindow to generate a URL for.
084:             */
085:            public PortletStateManager(PortletWindow window) {
086:                if (window == null)
087:                    throw new IllegalArgumentException("window cannot be null");
088:
089:                this .windowOfAction = (PortletWindowImpl) window;
090:                this .nextMode = null;
091:                this .nextState = null;
092:                this .isAction = false;
093:                this .nextAction = false;
094:
095:                if (windowOfAction.getChannelRuntimeData() != null
096:                        && windowOfAction.getHttpServletRequest() != null)
097:                    analyzeRequestInformation();
098:            }
099:
100:            /**
101:             * Sets the next portlet mode for the current PortletWindow
102:             * @param mode a portlet mode
103:             */
104:            public void setNextMode(PortletMode mode) {
105:                nextMode = mode;
106:            }
107:
108:            /**
109:             * Sets the next window state for the current PortletWindow
110:             * @param state a window state
111:             */
112:            public void setNextState(WindowState state) {
113:                nextState = state;
114:            }
115:
116:            /**
117:             * Setting the portlet action parameter for the next request 
118:             */
119:            public void setAction() {
120:                nextAction = true;
121:            }
122:
123:            /**
124:             * Adds the render parameters to the portlet URL
125:             * @param parameters a <code>Map</code> containing the render parameters
126:             */
127:            public void setParameters(Map parameters) {
128:                if (parameters != null && !parameters.isEmpty())
129:                    params.putAll(parameters);
130:            }
131:
132:            /**
133:             * Returns true if the current PortletRequest is ActionRequest,
134:             * false - otherwise
135:             */
136:            public boolean isAction() {
137:                return isAction;
138:            }
139:
140:            /**
141:             * Clears the render parameters for the current PortletWindow
142:             */
143:            public void clearParameters() {
144:                params.clear();
145:            }
146:
147:            /**
148:             * @see java.lang.Object#toString()
149:             */
150:            public String toString() {
151:                return this .getEncodedParameterString();
152:            }
153:
154:            /**
155:             * Returns a full portlet URL including uP file
156:             */
157:            public String getActionURL() {
158:                final StringBuffer actionUrl = new StringBuffer();
159:                final WindowState curState = getState(this .windowOfAction);
160:                final HttpServletRequest request = this .windowOfAction
161:                        .getHttpServletRequest();
162:                final ChannelRuntimeData runtimeData = this .windowOfAction
163:                        .getChannelRuntimeData();
164:
165:                //URLs are always absolute
166:                actionUrl.append(request.getContextPath());
167:                actionUrl.append("/");
168:
169:                //Get the appropriate URL base
170:                if (PortalContextProviderImpl.EXCLUSIVE.equals(this .nextState)
171:                        || (this .nextState == null && PortalContextProviderImpl.EXCLUSIVE
172:                                .equals(curState))) {
173:                    final String urlBase = runtimeData
174:                            .getBaseWorkerURL(UPFileSpec.FILE_DOWNLOAD_WORKER);
175:                    actionUrl.append(urlBase);
176:                } else {
177:                    final String urlBase = runtimeData.getBaseActionURL();
178:                    actionUrl.append(urlBase);
179:                }
180:
181:                actionUrl.append("?");
182:                actionUrl.append(this .getEncodedParameterString());
183:
184:                //Add the anchor to the URL
185:                if (ChannelManager.isUseAnchors()) {
186:                    actionUrl.append("#");
187:                    actionUrl.append(windowOfAction.getId());
188:                }
189:
190:                return actionUrl.toString();
191:            }
192:
193:            /**
194:             * Generates the string representation of the request parameters for
195:             * the portlet based on it's current state. 
196:             */
197:            private String getEncodedParameterString() {
198:                StringBuffer url = new StringBuffer();
199:                String windowId = windowOfAction.getId().toString();
200:
201:                if (nextAction)
202:                    url.append(ACTION + "=true&");
203:                else
204:                    url.append(ACTION + "=false&");
205:
206:                // Window state
207:                if (nextState != null) {
208:                    if (nextAction) {
209:                        url.append(UP_WINDOW_STATE + "=" + nextState + "&");
210:                    } else {
211:                        final WindowState curState;
212:                        if (isAction())
213:                            //Generating a URL during an action means the action is
214:                            //complete and this is the re-direct. We are actually interested
215:                            //in the previous WindowState which is the state the last
216:                            //render call took place in.
217:                            curState = getPrevState(windowOfAction);
218:                        else
219:                            curState = getState(windowOfAction);
220:
221:                        if (!nextState.equals(curState)) {
222:
223:                            //Switching to MINIMIZED, Goal is not focused and minimized
224:                            if (WindowState.MINIMIZED.equals(nextState)) {
225:                                url.append(UP_TCATTR + "=" + MINIMIZED + "&");
226:                                url.append(MIN_CHAN_ID + "=" + windowId + "&");
227:                                url.append(MINIMIZED + "_" + windowId
228:                                        + "_value=true&");
229:                            }
230:                            //Switching to NORMAL, Goal is not focused and not minimized
231:                            else if (WindowState.NORMAL.equals(nextState)) {
232:                                url.append(UP_ROOT + "=" + ROOT + "&");
233:                            }
234:                            //Switching to MAXIMIZED, Goal is focused and not minimized
235:                            else if (WindowState.MAXIMIZED.equals(nextState)) {
236:                                url.append(UP_ROOT + "=" + windowId + "&");
237:                            }
238:
239:                            //If our last state was minimized un-minimize it
240:                            if (WindowState.MINIMIZED.equals(curState)
241:                                    && !PortalContextProviderImpl.EXCLUSIVE
242:                                            .equals(nextState)) {
243:                                url.append(UP_TCATTR + "=" + MINIMIZED + "&");
244:                                url.append(MIN_CHAN_ID + "=" + windowId + "&");
245:                                url.append(MINIMIZED + "_" + windowId
246:                                        + "_value=false&");
247:                            }
248:                        }
249:                    }
250:                }
251:
252:                //  Portlet mode
253:                if (nextMode != null) {
254:
255:                    if (nextMode.equals(PortletMode.EDIT))
256:                        url.append(UP_EDIT_TARGET + "=" + windowId);
257:                    else if (nextMode.equals(PortletMode.HELP))
258:                        url.append(UP_HELP_TARGET + "=" + windowId);
259:                    else if (nextMode.equals(PortletMode.VIEW))
260:                        url.append(UP_VIEW_TARGET + "=" + windowId);
261:
262:                    url.append("&");
263:                }
264:
265:                // Other parameters
266:                for (final Iterator entryItr = params.entrySet().iterator(); entryItr
267:                        .hasNext();) {
268:                    final Map.Entry entry = (Map.Entry) entryItr.next();
269:                    String name = (String) entry.getKey();
270:
271:                    //Deals with parameters that start with the prefix string
272:                    if (name.startsWith(UP_PARAM_PREFIX)) {
273:                        name = encodeString(UP_PARAM_PREFIX + name);
274:                    } else {
275:                        name = encodeString(name);
276:                    }
277:
278:                    final Object value = entry.getValue();
279:                    final String[] values;
280:                    if (value instanceof  String[])
281:                        values = (String[]) value;
282:                    else
283:                        values = new String[] { value.toString() };
284:
285:                    for (int i = 0; i < values.length; i++) {
286:                        url.append(name);
287:                        url.append("=");
288:
289:                        if (values[i] != null)
290:                            url.append(encodeString(values[i]));
291:
292:                        url.append("&");
293:                    }
294:                }
295:
296:                while (url.charAt(url.length() - 1) == '&') {
297:                    url.deleteCharAt(url.length() - 1);
298:                }
299:
300:                for (int index = url.indexOf("&&"); index >= 0; index = url
301:                        .indexOf("&&")) {
302:                    url.deleteCharAt(index);
303:                }
304:
305:                return url.toString();
306:            }
307:
308:            /**
309:             * Analyzes the request parameters and sets portlet modes/window states for the current PortletWindow
310:             */
311:            private void analyzeRequestInformation() {
312:                this .params.clear();
313:                final String windowId = this .windowOfAction.getId().toString();
314:
315:                final ChannelRuntimeData runtimeData = this .windowOfAction
316:                        .getChannelRuntimeData();
317:                for (Iterator i = runtimeData.getParameters().keySet()
318:                        .iterator(); i.hasNext();) {
319:                    String paramName = (String) i.next();
320:                    String[] values = runtimeData.getParameterValues(paramName);
321:
322:                    if (ACTION.equals(paramName)) {
323:                        isAction = new Boolean(values[0]).booleanValue();
324:                    } else if (UP_HELP_TARGET.equals(paramName)
325:                            && windowId.equals(values[0])) {
326:                        setMode(windowOfAction, PortletMode.HELP);
327:                    } else if (UP_EDIT_TARGET.equals(paramName)
328:                            && windowId.equals(values[0])) {
329:                        setMode(windowOfAction, PortletMode.EDIT);
330:                    } else if (UP_VIEW_TARGET.equals(paramName)
331:                            && windowId.equals(values[0])) {
332:                        setMode(windowOfAction, PortletMode.VIEW);
333:                    } else if (UP_ROOT.equals(paramName)) {
334:                        if (!ROOT.equals(values[0]))
335:                            setState(windowOfAction, WindowState.MAXIMIZED);
336:                        else if (getPrevState(windowOfAction).equals(
337:                                WindowState.MAXIMIZED))
338:                            setState(windowOfAction, WindowState.NORMAL);
339:                    } else if (UP_TCATTR.equals(paramName)) {
340:                        if (MINIMIZED.equals(values[0])) {
341:                            String state = runtimeData.getParameter(MINIMIZED
342:                                    + "_" + windowId + "_value");
343:                            if (new Boolean(state).booleanValue())
344:                                setState(windowOfAction, WindowState.MINIMIZED);
345:                            else
346:                                setState(windowOfAction, WindowState.NORMAL);
347:                        }
348:                    }
349:                }
350:            }
351:
352:            /**
353:             * Generates the UTF-8 URL encoded version of the string, wrapping the 
354:             * possible UnsupportedEncodingException in a RuntimeException.
355:             * 
356:             * @param text The string to encode.
357:             * @return The encoded version of the string.
358:             */
359:            private String encodeString(String text) {
360:                try {
361:                    return URLEncoder.encode(text, "UTF-8");
362:                } catch (UnsupportedEncodingException e) {
363:                    LOG.error("Error URL encoding string to 'UTF-8'", e);
364:                    throw new RuntimeException(e);
365:                }
366:            }
367:
368:            /**
369:             * Generates the UTF-8 URL decoded version of the string, wrapping the 
370:             * possible UnsupportedEncodingException in a RuntimeException.
371:             * 
372:             * @param text The string to decode.
373:             * @return The decoded version of the string.
374:             */
375:            private String decodeString(String text) {
376:                try {
377:                    return URLDecoder.decode(text, "UTF-8");
378:                } catch (UnsupportedEncodingException e) {
379:                    LOG.error("Error URL decoding string to 'UTF-8'", e);
380:                    throw new RuntimeException(e);
381:                }
382:            }
383:
384:            /**
385:             * Clears the PorletMode and WindowState information for
386:             * the specifid PortletWindow
387:             * 
388:             * @param window The PortletWindow to clear the state information for
389:             */
390:            public static void clearState(PortletWindow window) {
391:                final HttpSession session = getSession(window);
392:
393:                if (session != null)
394:                    session.removeAttribute(getKey(window));
395:            }
396:
397:            /**
398:             * Returns the current portlet mode for the given PortletWindow
399:             * 
400:             * @param window a portlet window
401:             * @return a PortletMode instance
402:             */
403:            public static PortletMode getMode(PortletWindow window) {
404:                final PortletWindowStateInfo stateInfo = getStateInfo(window);
405:                return stateInfo.getCurrentMode();
406:            }
407:
408:            /**
409:             * Returns the previous portlet mode for the given PortletWindow
410:             * 
411:             * @param window a portlet window
412:             * @return a PortletMode instance
413:             */
414:            public static PortletMode getPrevMode(PortletWindow window) {
415:                final PortletWindowStateInfo stateInfo = getStateInfo(window);
416:                return stateInfo.getPreviousMode();
417:            }
418:
419:            /**
420:             * Returns the current portlet state for the given PortletWindow
421:             * 
422:             * @param window a portlet window
423:             * @return a WindowState instance
424:             */
425:            public static WindowState getState(PortletWindow window) {
426:                final PortletWindowStateInfo stateInfo = getStateInfo(window);
427:                return stateInfo.getCurrentState();
428:            }
429:
430:            /**
431:             * Returns the previous portlet state for the given PortletWindow
432:             * 
433:             * @param window a portlet window
434:             * @return a WindowState instance
435:             */
436:            public static WindowState getPrevState(PortletWindow window) {
437:                final PortletWindowStateInfo stateInfo = getStateInfo(window);
438:                return stateInfo.getPreviousState();
439:            }
440:
441:            /**
442:             * Sets the portlet mode for the given PortletWindow
443:             * 
444:             * @param window a portlet window
445:             * @param mode a portlet mode
446:             */
447:            public static void setMode(PortletWindow window, PortletMode mode) {
448:                final PortletWindowStateInfo stateInfo = getStateInfo(window);
449:                stateInfo.setCurrentMode(mode);
450:            }
451:
452:            /**
453:             * Sets the window state for the given PortletWindow
454:             * 
455:             * @param window a portlet window
456:             * @param state a window state
457:             */
458:            public static void setState(PortletWindow window, WindowState state) {
459:                final PortletWindowStateInfo stateInfo = getStateInfo(window);
460:                stateInfo.setCurrentState(state);
461:
462:                //Ensure the uPFile for the window is setup appropriately for the window state
463:                try {
464:                    final PortletWindowImpl windowImpl = (PortletWindowImpl) window;
465:                    final ChannelRuntimeData runtimeData = windowImpl
466:                            .getChannelRuntimeData();
467:
468:                    if (!PortalContextProviderImpl.EXCLUSIVE.equals(stateInfo
469:                            .getCurrentState())) {
470:                        runtimeData.getUPFile().setMethod(
471:                                UPFileSpec.RENDER_METHOD);
472:                        runtimeData.getUPFile().setMethodNodeId(
473:                                UserInstance.USER_LAYOUT_ROOT_NODE);
474:                    }
475:                } catch (PortalException pe) {
476:                    LOG
477:                            .error(
478:                                    "Error setting the uPFileSpec for a non-EXCLUSIVE URL",
479:                                    pe);
480:                }
481:            }
482:
483:            /**
484:             * Gets a HttpSession for the specified PortletWindow. Will create one
485:             * if needed.
486:             * 
487:             * @param window The PortletWindow to get the HttpSession for.
488:             * @return The HttpSession for the PortletWindow.
489:             * @throws IllegalStateException If the PortletWindow doesn't have an associated HttpServletRequest object.
490:             */
491:            private static HttpSession getSession(PortletWindow window) {
492:                final PortletWindowImpl windowImpl = (PortletWindowImpl) window;
493:                final HttpServletRequest request = windowImpl
494:                        .getHttpServletRequest();
495:
496:                /*
497:                 * Unwrap the HttpServletRequest!
498:                 * When this is getting called via the dispatched portlet it gets
499:                 * a HttpServletRequestWrapper generated by the cross context
500:                 * dispatch. If you dig deep enough in the HttpServletRequestWrapper
501:                 * layers you can find the original request which is needed here to
502:                 * ensure the correct session is returned.
503:                 */
504:                final HttpServletRequest realRequest = getUnwrapedRequest(request);
505:
506:                if (realRequest != null) {
507:                    return realRequest.getSession(true);
508:                } else {
509:                    throw new IllegalStateException(
510:                            "No HttpServletRequest could be found for PortletWindow.id='"
511:                                    + window.getId() + "'");
512:                }
513:            }
514:
515:            /**
516:             * Recursivly unwrapps a HttpServletRequest. Determines if the current object
517:             * implements HttpServletRequestWrapper, gets the wrapped request and tries to
518:             * un wrap it until a base HttpServletRequest is found.
519:             * 
520:             * @param request The request to unwrap.
521:             * @return The unwrapped request.
522:             */
523:            private static HttpServletRequest getUnwrapedRequest(
524:                    HttpServletRequest request) {
525:                if (request instanceof  HttpServletRequestWrapper) {
526:                    final HttpServletRequestWrapper wrapper = (HttpServletRequestWrapper) request;
527:                    final ServletRequest wrappedRequest = wrapper.getRequest();
528:
529:                    if (request != wrappedRequest
530:                            && wrappedRequest instanceof  HttpServletRequest)
531:                        return getUnwrapedRequest((HttpServletRequest) wrappedRequest);
532:                    else
533:                        return wrapper;
534:                } else {
535:                    return request;
536:                }
537:            }
538:
539:            /**
540:             * Gets PortletWindowStateInfo for the PortletWindow, creates one if
541:             * nessesary.
542:             * 
543:             * @param window The PortletWindow to get the PortletWindowStateInfo for.
544:             * @return The PortletWindowStateInfo for the PortletWindow.
545:             */
546:            private static PortletWindowStateInfo getStateInfo(
547:                    PortletWindow window) {
548:                final HttpSession session = getSession(window);
549:                final String stateKey = getKey(window);
550:
551:                synchronized (session) {
552:                    PortletWindowStateInfo stateInfo = (PortletWindowStateInfo) session
553:                            .getAttribute(stateKey);
554:
555:                    if (stateInfo == null) {
556:                        stateInfo = new PortletWindowStateInfo();
557:                        session.setAttribute(stateKey, stateInfo);
558:                    }
559:
560:                    return stateInfo;
561:                }
562:            }
563:
564:            /**
565:             * Generates a key from the PortletWindow id.
566:             * 
567:             * @param window The PortletWindow to generate the key from.
568:             * @return The key for the PortletWindow.
569:             */
570:            private static String getKey(PortletWindow window) {
571:                final StringBuffer keyBuf = new StringBuffer();
572:
573:                keyBuf.append(PortletWindowStateInfo.class.getName());
574:                keyBuf.append("_");
575:                keyBuf.append(window.getId());
576:
577:                return keyBuf.toString();
578:            }
579:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.