| /******************************************************************************* |
| * Copyright (c) 2013-2020 LAAS-CNRS (www.laas.fr) |
| * 7 Colonel Roche 31077 Toulouse - France |
| * |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * Initial Contributors: |
| * Thierry Monteil : Project manager, technical co-manager |
| * Mahdi Ben Alaya : Technical co-manager |
| * Samir Medjiah : Technical co-manager |
| * Khalil Drira : Strategy expert |
| * Guillaume Garzone : Developer |
| * François Aïssaoui : Developer |
| * |
| * New contributors : |
| *******************************************************************************/ |
| package org.eclipse.om2m.core.router; |
| |
| import java.math.BigInteger; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| import org.eclipse.om2m.commons.constants.Constants; |
| import org.eclipse.om2m.commons.constants.MimeMediaType; |
| import org.eclipse.om2m.commons.constants.Operation; |
| import org.eclipse.om2m.commons.constants.ResourceType; |
| import org.eclipse.om2m.commons.constants.ResponseStatusCode; |
| import org.eclipse.om2m.commons.constants.ResponseType; |
| import org.eclipse.om2m.commons.constants.ResultContent; |
| import org.eclipse.om2m.commons.constants.ShortName; |
| import org.eclipse.om2m.commons.exceptions.AccessDeniedException; |
| import org.eclipse.om2m.commons.exceptions.BadRequestException; |
| import org.eclipse.om2m.commons.exceptions.NotImplementedException; |
| import org.eclipse.om2m.commons.exceptions.Om2mException; |
| import org.eclipse.om2m.commons.exceptions.ResourceNotFoundException; |
| import org.eclipse.om2m.commons.resource.DynamicAuthorizationConsultation; |
| import org.eclipse.om2m.commons.resource.RequestPrimitive; |
| import org.eclipse.om2m.commons.resource.ResponsePrimitive; |
| import org.eclipse.om2m.core.controller.AEController; |
| import org.eclipse.om2m.core.controller.AccessControlPolicyController; |
| import org.eclipse.om2m.core.controller.AEAnncController; |
| import org.eclipse.om2m.core.controller.CSEBaseController; |
| import org.eclipse.om2m.core.controller.ContainerController; |
| import org.eclipse.om2m.core.controller.ContentInstanceController; |
| import org.eclipse.om2m.core.controller.Controller; |
| import org.eclipse.om2m.core.controller.DiscoveryController; |
| import org.eclipse.om2m.core.controller.DynamicAuthorizationConsultationController; |
| import org.eclipse.om2m.core.controller.FanOutPointController; |
| import org.eclipse.om2m.core.controller.FlexContainerAnncController; |
| import org.eclipse.om2m.core.controller.FlexContainerController; |
| import org.eclipse.om2m.core.controller.GroupController; |
| import org.eclipse.om2m.core.controller.LatestOldestController; |
| import org.eclipse.om2m.core.controller.LatestOldestController.SortingPolicy; |
| import org.eclipse.om2m.core.controller.MgmtObjAnncController; |
| import org.eclipse.om2m.core.controller.MgmtObjController; |
| import org.eclipse.om2m.core.controller.NodeAnncController; |
| import org.eclipse.om2m.core.controller.NodeController; |
| import org.eclipse.om2m.core.controller.PollingChannelController; |
| import org.eclipse.om2m.core.controller.PollingChannelUriController; |
| import org.eclipse.om2m.core.controller.RemoteCSEController; |
| import org.eclipse.om2m.core.controller.RequestController; |
| import org.eclipse.om2m.core.controller.SubscriptionController; |
| import org.eclipse.om2m.core.datamapper.DataMapperSelector; |
| import org.eclipse.om2m.core.nblocking.NonBlockingHandler; |
| import org.eclipse.om2m.core.redirector.Redirector; |
| import org.eclipse.om2m.core.service.CseService; |
| import org.eclipse.om2m.core.urimapper.UriMapper; |
| |
| /** |
| * Routes a generic request to the appropriate resource controller to handle it |
| * based on the request method and URI. |
| */ |
| |
| public class Router implements CseService { |
| /** Logger */ |
| private static Log LOGGER = LogFactory.getLog(Router.class); |
| |
| /** |
| * Invokes required resource controller method. |
| * |
| * @param request |
| * - The generic request to handle |
| * @return The generic returned response |
| */ |
| public ResponsePrimitive doRequest(RequestPrimitive request) { |
| |
| LOGGER.info("Received request in Router: " + request.toString()); |
| ResponsePrimitive response = new ResponsePrimitive(request); |
| Patterns patterns = new Patterns(); |
| |
| String contentFormat = System.getProperty("org.eclipse.om2m.registration.contentFormat", MimeMediaType.XML); |
| |
| try { |
| |
| // Non blocking request handling |
| if (request.getResponseTypeInfo() != null && request.getResponseTypeInfo().getResponseType() != null) { |
| if (request.getResponseTypeInfo().getResponseType().equals(ResponseType.NON_BLOCKING_REQUEST_SYNCH) |
| || request.getResponseTypeInfo().getResponseType() |
| .equals(ResponseType.NON_BLOCKING_REQUEST_ASYNCH)) { |
| if (Constants.NON_BLOCKING_SUPPORTED) { |
| return NonBlockingHandler.handle(request); |
| } else { |
| response.setResponseStatusCode(ResponseStatusCode.NON_BLOCKING_REQUEST_NOT_SUPPORTED); |
| response.setContent("Non blocking request is not supported"); |
| response.setContentType(MimeMediaType.TEXT_PLAIN); |
| return response; |
| } |
| } |
| } |
| |
| // Check if the data type has been set |
| if (request.getRequestContentType() == null) { |
| request.setRequestContentType(contentFormat); |
| LOGGER.info("No Content-Type parameter set, setting to default: " + request.getRequestContentType()); |
| } |
| if (request.getReturnContentType() == null) { |
| request.setReturnContentType(contentFormat); |
| LOGGER.info("No Accept parameter set, setting to default: " + request.getReturnContentType()); |
| } |
| |
| // Check if the data type requested is supported |
| if (request.getRequestContentType() != MimeMediaType.OBJ && request.getRequestContentType() != null) { |
| if (!DataMapperSelector.getDataMapperList().containsKey(request.getRequestContentType())) { |
| throw new NotImplementedException(request.getRequestContentType() + " is not supported."); |
| } |
| } |
| if (request.getReturnContentType() != MimeMediaType.OBJ && request.getReturnContentType() != null) { |
| if (!DataMapperSelector.getDataMapperList().containsKey(request.getReturnContentType())) { |
| throw new NotImplementedException(request.getReturnContentType() + " is not supported."); |
| } |
| } |
| |
| // Check if the operation has been provided |
| if (request.getOperation() == null) { |
| throw new BadRequestException("No Operation provided"); |
| } |
| |
| // URI Handling |
| if (request.getTo() == null) { |
| throw new BadRequestException("No To/TargetId parameter provided"); |
| } |
| |
| if (request.getTo().startsWith("~")) { |
| request.setTo(request.getTo().substring(1)); |
| } |
| |
| // Check if the SP-ID is provided |
| if (request.getTo().startsWith("//") || request.getTo().startsWith("_")) { |
| String uri = request.getTo().substring(2); |
| String spId = uri.split("/")[0]; |
| if (!spId.equals(Constants.M2M_SP_ID)) { |
| throw new ResourceNotFoundException("Not the current SP Domain (" + spId + ")"); |
| } else { |
| request.setTo(uri.replace(spId, "")); |
| } |
| } else if (!request.getTo().startsWith("/")) { |
| request.setTo("/" + Constants.CSE_ID + "/" + request.getTo()); |
| } |
| |
| // Remove the last "/" from the request uri if exist. |
| if (request.getTo().endsWith("/")) { |
| request.setTo(request.getTo().substring(0, request.getTo().length() - 1)); |
| } |
| |
| getQueryStringFromTargetId(request); |
| |
| // Redirection case |
| if (!patterns.match(patterns.NON_RETARGETING_PATTERN, request.getTo())) { |
| LOGGER.info("Request targeting another CSE, forwarding to Redirector: " + request.getTo()); |
| return Redirector.retarget(request); |
| } |
| LOGGER.info("Request handling in the current CSE: " + request.getTo()); |
| |
| Controller controller = null; |
| // Case of hierarchical URI, retrieve the non-hierarchical URI of the resource |
| if (patterns.match(patterns.HIERARCHICAL_PATTERN, request.getTo())) { |
| if (request.getTo().contains(patterns.FANOUT_POINT_MATCH + "/")) { |
| int foptIndex = request.getTo().indexOf(patterns.FANOUT_POINT_MATCH); |
| String uri = request.getTo().substring(0, foptIndex); |
| String suffix = request.getTo().substring(foptIndex + patterns.FANOUT_POINT_MATCH.length(), |
| request.getTo().length()); |
| controller = new FanOutPointController(suffix); |
| request.setTo(uri); |
| LOGGER.info("Fan Out request received: [grp uri: " + uri + ", suffix: " + suffix + "]"); |
| } |
| if (request.getTo().endsWith(patterns.FANOUT_POINT_MATCH)) { |
| controller = new FanOutPointController(); |
| request.setTo(request.getTo().replaceAll(patterns.FANOUT_POINT_MATCH, "")); |
| LOGGER.info("Fan Out request received: [grp uri: " + request.getTo() + "]"); |
| } |
| if (request.getTo().endsWith("/" + ShortName.LATEST)) { |
| controller = new LatestOldestController(SortingPolicy.LATEST); |
| request.setTo(request.getTo() + "/"); |
| request.setTo(request.getTo().replace("/" + ShortName.LATEST + "/", "")); |
| } |
| if (request.getTo().endsWith("/" + ShortName.OLDEST)) { |
| controller = new LatestOldestController(SortingPolicy.OLDEST); |
| request.setTo(request.getTo() + "/"); |
| request.setTo(request.getTo().replace("/" + ShortName.OLDEST + "/", "")); |
| } |
| String nonHierarchicalUri = UriMapper.getNonHierarchicalUri(request.getTo()); |
| if (nonHierarchicalUri == null) { |
| throw new ResourceNotFoundException("Resource not found"); |
| } |
| request.setTo(nonHierarchicalUri); |
| LOGGER.debug("Changing to unstructured uri for routing to: " + request.getTo()); |
| } |
| |
| // Notify case |
| if (request.getOperation().equals(Operation.NOTIFY)) { |
| return Redirector.retargetNotify(request); |
| } |
| |
| // Discovery case |
| if ((request.getFilterCriteria() != null) && (request.getFilterCriteria().getFilterUsage() != null) |
| && (request.getFilterCriteria().getFilterUsage().intValue() == 1)) { |
| controller = new DiscoveryController(); |
| } |
| |
| // Determine the appropriate resource controller |
| // In case of a CREATE, the resource type determine the controller |
| if (controller == null) { |
| if (request.getOperation().equals(Operation.CREATE)) { |
| controller = getResourceControllerFromRT(request.getResourceType()); |
| } else { |
| controller = getResourceControllerFromURI(request.getTo()); |
| } |
| } |
| |
| if (controller != null) { |
| LOGGER.info("ResourceController to be used [" + controller.getClass().getSimpleName() + "]"); |
| // Perform the request in the specific controller |
| response = controller.doRequest(request); |
| if (request.getResultContent() != null && request.getResultContent().equals(ResultContent.NOTHING)) { |
| response.setContent(null); |
| } else { |
| if (response.getContent() != null && !(response.getContent() instanceof String) |
| && !request.getReturnContentType().equals(MimeMediaType.OBJ)) { |
| String representation = DataMapperSelector.getDataMapperList() |
| .get(request.getReturnContentType()).objToString(response.getContent()); |
| response.setContent(representation); |
| response.setContentType(request.getReturnContentType()); |
| } |
| } |
| } else { |
| throw new BadRequestException("Malformed URI"); |
| } |
| } catch (AccessDeniedException accessDeniedException) { |
| response.setResponseStatusCode(accessDeniedException.getErrorStatusCode()); |
| response.setContent(accessDeniedException.getMessage()); |
| response.setContentType(MimeMediaType.TEXT_PLAIN); |
| if (accessDeniedException.getDynAuthTokenReqInfo() != null) { |
| response.setContentType(request.getReturnContentType()); |
| response.setContent(DataMapperSelector.getDataMapperList().get(request.getReturnContentType()) |
| .objToString(accessDeniedException.getDynAuthTokenReqInfo())); |
| } |
| LOGGER.error("AccessDeniedException caught in Router: " + accessDeniedException.getMessage(), |
| accessDeniedException); |
| } catch (Om2mException om2mException) { |
| response.setResponseStatusCode(om2mException.getErrorStatusCode()); |
| response.setContent(om2mException.getMessage()); |
| response.setContentType(MimeMediaType.TEXT_PLAIN); |
| LOGGER.error("OM2M exception caught in Router: " + om2mException.getMessage(), om2mException); |
| LOGGER.debug("OM2M exception caught in Router", om2mException); |
| } catch (Exception e) { |
| LOGGER.error("Router internal error", e); |
| response.setResponseStatusCode(ResponseStatusCode.INTERNAL_SERVER_ERROR); |
| response.setContent("Router internal error"); |
| response.setContentType(MimeMediaType.TEXT_PLAIN); |
| } |
| |
| LOGGER.info("Response in Router= " + response); |
| return response; |
| } |
| |
| /** |
| * Finds required resource controller based on uri patterns. |
| * |
| * @param uri |
| * - Generic request uri |
| * @param method |
| * - Generic request method |
| * @param representation |
| * - Resource representation |
| * @return The matched resource controller otherwise null |
| */ |
| protected Controller getResourceControllerFromURI(String uri) { |
| Patterns patterns = new Patterns(); |
| // Match the resource controller with an uri pattern and return it, otherwise |
| // return null |
| if (patterns.match(patterns.CSE_BASE_PATTERN, uri)) { |
| return new CSEBaseController(); |
| } |
| if (patterns.match(patterns.AE_PATTERN, uri)) { |
| return new AEController(); |
| } |
| if (patterns.match(patterns.AEANNC_PATTERN, uri)) { |
| return new AEAnncController(); |
| } |
| if (patterns.match(patterns.ACP_PATTERN, uri)) { |
| return new AccessControlPolicyController(); |
| } |
| if (patterns.match(patterns.CONTAINER_PATTERN, uri)) { |
| return new ContainerController(); |
| } |
| if (patterns.match(patterns.DYNAMIC_AUTHORIZATION_CONSULTATION_PATTERN, uri)) { |
| return new DynamicAuthorizationConsultationController(); |
| } |
| if (patterns.match(patterns.FLEXCONTAINER_PATTERN, uri)) { |
| return new FlexContainerController(); |
| } |
| if (patterns.match(patterns.FLEXCONTAINER_ANNC_PATTERN, uri)) { |
| return new FlexContainerAnncController(); |
| } |
| if (patterns.match(patterns.CONTENTINSTANCE_PATTERN, uri)) { |
| return new ContentInstanceController(); |
| } |
| if (patterns.match(patterns.REMOTE_CSE_PATTERN, uri)) { |
| return new RemoteCSEController(); |
| } |
| if (patterns.match(patterns.GROUP_PATTERN, uri)) { |
| return new GroupController(); |
| } |
| if (patterns.match(patterns.NODE_PATTERN, uri)) { |
| return new NodeController(); |
| } |
| if (patterns.match(patterns.NODE_ANNC_PATTERN, uri)) { |
| return new NodeAnncController(); |
| } |
| if (patterns.match(patterns.NMGMT_OBJ_PATTERN, uri)) { |
| return new MgmtObjController(); |
| } |
| if (patterns.match(patterns.NMGMT_OBJ_ANNC_PATTERN, uri)) { |
| return new MgmtObjAnncController(); |
| } |
| if (patterns.match(patterns.SUBSCRIPTION_PATTERN, uri)) { |
| return new SubscriptionController(); |
| } |
| if (patterns.match(patterns.POLLING_CHANNEL_PATTERN, uri)) { |
| return new PollingChannelController(); |
| } |
| if (patterns.match(patterns.POLLING_CHANNEL_URI_PATTERN, uri)) { |
| return new PollingChannelUriController(); |
| } |
| if (patterns.match(patterns.REQUEST_PATTERN, uri)) { |
| return new RequestController(); |
| } |
| return null; |
| } |
| |
| /** |
| * In the case of a CREATE operation, the controller is determined by the |
| * resource type argument from the generic request primitive. |
| * |
| * @param resourceType |
| * @return the matching controller |
| */ |
| protected Controller getResourceControllerFromRT(BigInteger resourceType) { |
| // test in case resourceType is null |
| if (resourceType == null) { |
| throw new BadRequestException("Resource type is missing for creation request"); |
| } |
| // switch case |
| switch (resourceType.intValue()) { |
| case ResourceType.CSE_BASE: |
| return new CSEBaseController(); |
| case ResourceType.ACCESS_CONTROL_POLICY: |
| return new AccessControlPolicyController(); |
| case ResourceType.AE: |
| return new AEController(); |
| case ResourceType.CONTAINER: |
| return new ContainerController(); |
| case ResourceType.CONTENT_INSTANCE: |
| return new ContentInstanceController(); |
| case ResourceType.DYNAMIC_AUTHORIZATION_CONSULTATION: |
| return new DynamicAuthorizationConsultationController(); |
| case ResourceType.REMOTE_CSE: |
| return new RemoteCSEController(); |
| case ResourceType.GROUP: |
| return new GroupController(); |
| case ResourceType.NODE: |
| return new NodeController(); |
| case ResourceType.SUBSCRIPTION: |
| return new SubscriptionController(); |
| case ResourceType.POLLING_CHANNEL: |
| return new PollingChannelController(); |
| case ResourceType.FLEXCONTAINER: |
| return new FlexContainerController(); |
| case ResourceType.AE_ANNC: |
| return new AEAnncController(); |
| case ResourceType.FLEXCONTAINER_ANNC: |
| return new FlexContainerAnncController(); |
| case ResourceType.MGMT_OBJ: |
| return new MgmtObjController(); |
| case ResourceType.MGMT_OBJ_ANNC: |
| return new MgmtObjAnncController(); |
| case ResourceType.NODE_ANNC: |
| return new NodeAnncController(); |
| default: |
| throw new NotImplementedException("ResourceType: " + resourceType + " is not implemented"); |
| } |
| } |
| |
| private static void getQueryStringFromTargetId(RequestPrimitive request) { |
| |
| if (request.getTo().contains("#")) { |
| request.getQueryStrings().put("#", new ArrayList()); |
| } |
| if (request.getTo().contains("?")) { |
| String query = request.getTo().split("\\?")[1]; |
| Map<String, List<String>> parameters = new HashMap<String, List<String>>(); |
| if (query != null) { |
| String[] pairs = query.split("[&]"); |
| for (String pair : pairs) { |
| String[] param = pair.split("[=]"); |
| |
| String key = null; |
| String value = null; |
| if (param.length > 0) { |
| key = param[0]; |
| } |
| if (param.length > 1) { |
| value = param[1]; |
| } |
| if (parameters.containsKey(key)) { |
| parameters.get(key).add(value); |
| } else { |
| List<String> values = new ArrayList<String>(); |
| values.add(value); |
| parameters.put(key, values); |
| } |
| } |
| } |
| request.getQueryStrings().putAll(parameters); |
| |
| } |
| |
| } |
| |
| } |