View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.syncope.core.rest.cxf.service;
20  
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.time.OffsetDateTime;
24  import java.util.List;
25  import java.util.Optional;
26  import javax.ws.rs.InternalServerErrorException;
27  import javax.ws.rs.NotFoundException;
28  import javax.ws.rs.core.HttpHeaders;
29  import javax.ws.rs.core.MediaType;
30  import javax.ws.rs.core.Response;
31  import javax.ws.rs.core.StreamingOutput;
32  import org.apache.commons.lang3.StringUtils;
33  import org.apache.commons.lang3.tuple.Pair;
34  import org.apache.cxf.Bus;
35  import org.apache.cxf.transport.DestinationFactoryManager;
36  import org.apache.cxf.transport.http.DestinationRegistry;
37  import org.apache.cxf.transport.http.HTTPTransportFactory;
38  import org.apache.syncope.common.lib.SyncopeClientException;
39  import org.apache.syncope.common.lib.SyncopeConstants;
40  import org.apache.syncope.common.lib.to.GroupTO;
41  import org.apache.syncope.common.lib.to.PagedResult;
42  import org.apache.syncope.common.lib.to.TypeExtensionTO;
43  import org.apache.syncope.common.lib.types.ClientExceptionType;
44  import org.apache.syncope.common.rest.api.Preference;
45  import org.apache.syncope.common.rest.api.RESTHeaders;
46  import org.apache.syncope.common.rest.api.batch.BatchPayloadParser;
47  import org.apache.syncope.common.rest.api.batch.BatchRequestItem;
48  import org.apache.syncope.common.rest.api.service.SyncopeService;
49  import org.apache.syncope.core.logic.SyncopeLogic;
50  import org.apache.syncope.core.persistence.api.dao.BatchDAO;
51  import org.apache.syncope.core.persistence.api.entity.Batch;
52  import org.apache.syncope.core.persistence.api.entity.EntityFactory;
53  import org.apache.syncope.core.rest.cxf.batch.BatchProcess;
54  import org.apache.syncope.core.spring.ApplicationContextProvider;
55  import org.apache.syncope.core.spring.security.AuthContextUtils;
56  import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
57  import org.springframework.security.core.context.SecurityContextHolder;
58  import org.springframework.stereotype.Service;
59  
60  @Service
61  public class SyncopeServiceImpl extends AbstractService implements SyncopeService {
62  
63      private static final String CONTENT_XML = "Content.xml";
64  
65      protected final SyncopeLogic logic;
66  
67      protected final ThreadPoolTaskExecutor batchExecutor;
68  
69      protected final Bus bus;
70  
71      protected final BatchDAO batchDAO;
72  
73      protected final EntityFactory entityFactory;
74  
75      public SyncopeServiceImpl(
76              final SyncopeLogic logic,
77              final ThreadPoolTaskExecutor batchExecutor,
78              final Bus bus,
79              final BatchDAO batchDAO,
80              final EntityFactory entityFactory) {
81  
82          this.logic = logic;
83          this.batchExecutor = batchExecutor;
84          this.bus = bus;
85          this.batchDAO = batchDAO;
86          this.entityFactory = entityFactory;
87      }
88  
89      @Override
90      public PagedResult<GroupTO> searchAssignableGroups(
91              final String realm, final String term, final int page, final int size) {
92  
93          Pair<Integer, List<GroupTO>> result = logic.searchAssignableGroups(
94                  StringUtils.prependIfMissing(realm, SyncopeConstants.ROOT_REALM), term, page, size);
95          return buildPagedResult(result.getRight(), page, size, result.getLeft());
96      }
97  
98      @Override
99      public TypeExtensionTO readUserTypeExtension(final String groupName) {
100         return logic.readTypeExtension(groupName);
101     }
102 
103     private DestinationRegistry getDestinationRegistryFromBusOrDefault() {
104         DestinationFactoryManager dfm = bus.getExtension(DestinationFactoryManager.class);
105         try {
106             HTTPTransportFactory df = (HTTPTransportFactory) dfm.
107                     getDestinationFactory("http://cxf.apache.org/transports/http/configuration");
108             return df.getRegistry();
109         } catch (Exception e) {
110             throw new InternalServerErrorException("Could not find CXF's DestinationRegistry", e);
111         }
112     }
113 
114     @Override
115     public Response batch(final InputStream input) {
116         // parse Content-Type, expect appropriate boundary
117         MediaType mediaType = MediaType.valueOf(messageContext.getHttpServletRequest().getContentType());
118         String boundary = mediaType.getParameters().get(RESTHeaders.BOUNDARY_PARAMETER);
119 
120         if (batchDAO.find(boundary) != null) {
121             SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.EntityExists);
122             sce.getElements().add("Batch with boundary " + boundary + " already processing");
123             throw sce;
124         }
125 
126         // parse batch request
127         List<BatchRequestItem> batchRequestItems;
128         try {
129             batchRequestItems = BatchPayloadParser.parse(input, mediaType, new BatchRequestItem());
130         } catch (IOException e) {
131             LOG.error("Could not parse batch request with boundary {}", boundary, e);
132 
133             SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidEntity);
134             sce.getElements().add("Batch request with boundary " + boundary);
135             throw sce;
136         }
137 
138         // prepare for batch processing
139         Batch batch = entityFactory.newEntity(Batch.class);
140         batch.setKey(boundary);
141         batch.setExpiryTime(OffsetDateTime.now().plusMinutes(5));
142         batchDAO.save(batch);
143 
144         BatchProcess batchProcess = ApplicationContextProvider.getBeanFactory().createBean(BatchProcess.class);
145         batchProcess.setBoundary(boundary);
146         batchProcess.setBasePath(uriInfo.getBaseUri().toASCIIString());
147         batchProcess.setBatchRequestItems(batchRequestItems);
148         batchProcess.setDestinationRegistry(getDestinationRegistryFromBusOrDefault());
149         batchProcess.setServletConfig(messageContext.getServletConfig());
150         batchProcess.setServletRequest(messageContext.getHttpServletRequest());
151         batchProcess.setAuthentication(SecurityContextHolder.getContext().getAuthentication());
152 
153         // manage synchronous Vs asynchronous batch processing
154         if (getPreference() == Preference.RESPOND_ASYNC) {
155             batchExecutor.execute(batchProcess);
156 
157             return Response.accepted().
158                     header(RESTHeaders.PREFERENCE_APPLIED, getPreference().toString()).
159                     header(HttpHeaders.LOCATION, uriInfo.getAbsolutePathBuilder().build()).
160                     type(RESTHeaders.multipartMixedWith(boundary)).
161                     build();
162         } else {
163             batchProcess.run();
164             return batch();
165         }
166     }
167 
168     @Override
169     public Response batch() {
170         MediaType mediaType = MediaType.valueOf(messageContext.getHttpServletRequest().getContentType());
171         String boundary = mediaType.getParameters().get(RESTHeaders.BOUNDARY_PARAMETER);
172 
173         Batch batch = Optional.ofNullable(batchDAO.find(boundary)).
174                 orElseThrow(() -> new NotFoundException("Batch " + boundary));
175 
176         if (batch.getResults() == null) {
177             return Response.accepted().
178                     type(RESTHeaders.multipartMixedWith(boundary)).
179                     header(HttpHeaders.RETRY_AFTER, 5).
180                     header(HttpHeaders.LOCATION, uriInfo.getAbsolutePathBuilder().build()).
181                     build();
182         }
183 
184         Response response = Response.ok(batch.getResults()).
185                 type(RESTHeaders.multipartMixedWith(boundary)).
186                 build();
187 
188         batchDAO.delete(boundary);
189 
190         return response;
191     }
192 
193     @Override
194     public Response exportInternalStorageContent(final int tableThreshold) {
195         StreamingOutput sout = os -> logic.exportInternalStorageContent(tableThreshold, os);
196 
197         return Response.ok(sout).
198                 type(MediaType.TEXT_XML).
199                 header(HttpHeaders.CONTENT_DISPOSITION,
200                         "attachment; filename=" + AuthContextUtils.getDomain() + CONTENT_XML).
201                 build();
202     }
203 }