| /******************************************************************************* |
| * Copyright (c) 2013-2016 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 v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * 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.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.RequestPrimitive; |
| import org.eclipse.om2m.commons.resource.ResponsePrimitive; |
| import org.eclipse.om2m.core.controller.AEAnncController; |
| import org.eclipse.om2m.core.controller.AEController; |
| import org.eclipse.om2m.core.controller.AccessControlPolicyController; |
| 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.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) { |
| if (request.getTargetId() == null) { |
| throw new BadRequestException("No To/TargetId parameter provided"); |
| } else { |
| request.setTo(request.getTargetId()); |
| } |
| } else if (request.getTargetId() == null) { |
| request.setTargetId(request.getTo()); |
| } |
| |
| if(request.getTargetId().startsWith("~")){ |
| request.setTargetId(request.getTargetId().substring(1)); |
| } |
| |
| // Check if the SP-ID is provided |
| if(request.getTargetId().startsWith("//") || request.getTargetId().startsWith("_")){ |
| String uri = request.getTargetId().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.setTargetId(uri.replace(spId, "")); |
| } |
| } else if (!request.getTargetId().startsWith("/")){ |
| request.setTargetId("/" + Constants.CSE_ID + "/" + request.getTargetId()); |
| } |
| |
| // Remove the last "/" from the request uri if exist. |
| if(request.getTargetId().endsWith("/")){ |
| request.setTargetId(request.getTargetId().substring(0,request.getTargetId().length()-1)); |
| } |
| |
| getQueryStringFromTargetId(request); |
| |
| // Redirection case |
| if (!patterns.match(patterns.NON_RETARGETING_PATTERN, request.getTargetId())){ |
| 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.getTargetId())){ |
| if(request.getTargetId().contains(patterns.FANOUT_POINT_MATCH + "/")){ |
| int foptIndex = request.getTargetId().indexOf(patterns.FANOUT_POINT_MATCH); |
| String uri = request.getTargetId().substring(0, foptIndex); |
| String suffix = request.getTargetId() |
| .substring( |
| foptIndex + patterns.FANOUT_POINT_MATCH.length(), |
| request.getTargetId().length() |
| ); |
| controller = new FanOutPointController(suffix); |
| request.setTargetId(uri); |
| LOGGER.info("Fan Out request received: [grp uri: " + uri + ", suffix: " + suffix + "]"); |
| } if (request.getTargetId().endsWith(patterns.FANOUT_POINT_MATCH)) { |
| controller = new FanOutPointController(); |
| request.setTargetId(request.getTargetId().replaceAll(patterns.FANOUT_POINT_MATCH, "")); |
| LOGGER.info("Fan Out request received: [grp uri: " + request.getTargetId()+ "]"); |
| } |
| if(request.getTargetId().endsWith("/" + ShortName.LATEST)){ |
| controller = new LatestOldestController(SortingPolicy.LATEST); |
| request.setTargetId(request.getTargetId() + "/"); |
| request.setTargetId(request.getTargetId().replace("/"+ShortName.LATEST+"/", "")); |
| } |
| if (request.getTargetId().endsWith("/" + ShortName.OLDEST)){ |
| controller = new LatestOldestController(SortingPolicy.OLDEST); |
| request.setTargetId(request.getTargetId() + "/"); |
| request.setTargetId(request.getTargetId().replace("/"+ShortName.OLDEST+"/", "")); |
| } |
| String nonHierarchicalUri = UriMapper.getNonHierarchicalUri(request.getTargetId()); |
| if (nonHierarchicalUri == null){ |
| throw new ResourceNotFoundException("Resource not found"); |
| } |
| request.setTargetId(nonHierarchicalUri); |
| LOGGER.debug("Changing to unstructured uri for routing to: " + request.getTargetId()); |
| } |
| |
| // 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.getTargetId()); |
| } |
| } |
| |
| 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 (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.NMGMT_OBJ_PATTERN, uri)) { |
| return new MgmtObjController(); |
| } |
| 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(); |
| 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.getTargetId().contains("?")){ |
| String query = request.getTargetId().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); |
| |
| if(request.getTo() == null){ |
| request.setTo(request.getTargetId().split("\\?")[0]); |
| request.setTargetId(request.getTo()); |
| } |
| } |
| |
| } |
| |
| } |