1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.opencastproject.oaipmh.server;
22
23 import static org.opencastproject.oaipmh.util.OsgiUtil.checkDictionary;
24 import static org.opencastproject.oaipmh.util.OsgiUtil.getCfg;
25 import static org.opencastproject.oaipmh.util.OsgiUtil.getContextProperty;
26 import static org.opencastproject.util.data.Collections.map;
27 import static org.opencastproject.util.data.functions.Strings.trimToNil;
28
29 import org.opencastproject.oaipmh.util.XmlGen;
30 import org.opencastproject.security.api.SecurityService;
31 import org.opencastproject.util.OsgiUtil;
32 import org.opencastproject.util.UrlSupport;
33
34 import org.apache.commons.lang3.StringUtils;
35 import org.osgi.framework.ServiceRegistration;
36 import org.osgi.service.cm.ConfigurationException;
37 import org.osgi.service.component.ComponentContext;
38 import org.osgi.service.component.annotations.Activate;
39 import org.osgi.service.component.annotations.Component;
40 import org.osgi.service.component.annotations.Deactivate;
41 import org.osgi.service.component.annotations.Modified;
42 import org.osgi.service.component.annotations.Reference;
43 import org.osgi.service.component.annotations.ReferenceCardinality;
44 import org.osgi.service.component.annotations.ReferencePolicy;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48 import java.io.IOException;
49 import java.util.Arrays;
50 import java.util.Dictionary;
51 import java.util.Map;
52 import java.util.Optional;
53
54 import javax.servlet.ServletException;
55 import javax.servlet.http.HttpServlet;
56 import javax.servlet.http.HttpServletRequest;
57 import javax.servlet.http.HttpServletResponse;
58
59
60 @Component(
61 immediate = true,
62 service = { OaiPmhServerInfo.class, OaiPmhServer.class },
63 property = {
64 "service.description=OAI-PMH server"
65 }
66 )
67 public final class OaiPmhServer extends HttpServlet implements OaiPmhServerInfo {
68
69 private static final long serialVersionUID = -7536526468920288612L;
70
71 private static final Logger logger = LoggerFactory.getLogger(OaiPmhServer.class);
72
73 private static final String CFG_DEFAULT_REPOSITORY = "default-repository";
74 private static final String CFG_OAIPMH_MOUNTPOINT = "org.opencastproject.oaipmh.mountpoint";
75 private static final String CFG_DEFAULT_OAIPMH_MOUNTPOINT = "/oaipmh";
76
77 private SecurityService securityService;
78
79 private final Map<String, OaiPmhRepository> repositories = map();
80
81 private ComponentContext componentContext;
82
83 private String defaultRepo;
84
85
86
87
88 private String mountPoint;
89
90 private ServiceRegistration<?> serviceRegistration;
91
92
93 @Reference(
94 cardinality = ReferenceCardinality.MULTIPLE,
95 policy = ReferencePolicy.DYNAMIC,
96 unbind = "unsetRepository"
97 )
98 public void setRepository(final OaiPmhRepository r) {
99 synchronized (repositories) {
100 final String rId = r.getRepositoryId();
101 if (repositories.containsKey(rId)) {
102 logger.error("A repository with id {} has already been registered", rId);
103 } else {
104
105 repositories.put(rId, r);
106 logger.info("Registered repository " + rId);
107 }
108 }
109 }
110
111
112 public void unsetRepository(OaiPmhRepository r) {
113 synchronized (repositories) {
114 repositories.remove(r.getRepositoryId());
115 logger.info("Unregistered repository " + r.getRepositoryId());
116 }
117 }
118
119
120 @Reference
121 public void setSecurityService(SecurityService securityService) {
122 this.securityService = securityService;
123 }
124
125
126 @Activate
127 public void activate(ComponentContext cc) throws ConfigurationException {
128 logger.info("Activate");
129 this.componentContext = cc;
130
131 try {
132 mountPoint = UrlSupport.concat("/", StringUtils.trimToNull(getContextProperty(componentContext, CFG_OAIPMH_MOUNTPOINT)));
133 } catch (RuntimeException e) {
134 mountPoint = CFG_DEFAULT_OAIPMH_MOUNTPOINT;
135 }
136 updated(cc.getProperties());
137 }
138
139 @Modified
140 public void modified(ComponentContext cc) throws ConfigurationException {
141 logger.info("Updated");
142 updated(cc.getProperties());
143 }
144
145 @Deactivate
146 public void deactivate() {
147 tryUnregisterServlet();
148 }
149
150
151 public synchronized void updated(Dictionary<String, ?> properties) throws ConfigurationException {
152
153
154
155 checkDictionary(properties, componentContext);
156 defaultRepo = getCfg(properties, CFG_DEFAULT_REPOSITORY);
157
158 try {
159
160 tryUnregisterServlet();
161 logger.info("Registering OAI-PMH server under " + mountPoint);
162 logger.info("Default repository is " + defaultRepo);
163
164 serviceRegistration = OsgiUtil.registerServlet(componentContext.getBundleContext(), this, mountPoint);
165 } catch (Exception e) {
166 logger.error("Error registering OAI-PMH servlet", e);
167 throw new RuntimeException("Error registering OAI-PMH servlet", e);
168 }
169 logger.info("There are {} repositories registered yet. Watch out for later registration messages.",
170 repositories.values().size());
171 }
172
173 @Override
174 protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
175 dispatch(req, res);
176 }
177
178 @Override
179 protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
180 dispatch(req, res);
181 }
182
183 private void dispatch(final HttpServletRequest req, final HttpServletResponse res) throws IOException {
184 try {
185 Optional<String> serverUrl = OaiPmhServerInfoUtil.oaiPmhServerUrlOfCurrentOrganization(securityService);
186 if (serverUrl.isPresent()) {
187 Optional<String> repositoryId = repositoryId(req, mountPoint);
188 if (repositoryId.isPresent()) {
189 if (runRepo(repositoryId.get(), serverUrl.get(), req, res)) {
190 return;
191 } else {
192 res.sendError(HttpServletResponse.SC_NOT_FOUND);
193 return;
194 }
195 }
196
197 if (runRepo(defaultRepo, serverUrl.get(), req, res)) {
198 return;
199 }
200 }
201 res.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
202 } catch (Exception e) {
203 logger.error("Error handling OAI-PMH request", e);
204 res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
205 }
206 }
207
208
209
210
211
212
213 private boolean runRepo(String repoId, String serverUrl, HttpServletRequest req, HttpServletResponse res)
214 throws Exception {
215 Optional<OaiPmhRepository> repo = getRepoById(repoId);
216 if (repo.isPresent()) {
217 final String repoUrl = UrlSupport.concat(serverUrl, mountPoint, repoId);
218 runRepo(repo.get(), repoUrl, req, res);
219 return true;
220 }
221 return false;
222 }
223
224 private void runRepo(OaiPmhRepository repo, final String repoUrl, final HttpServletRequest req,
225 HttpServletResponse res) throws Exception {
226 final Params p = new Params() {
227 @Override
228 String getParameter(String key) {
229 return req.getParameter(key);
230 }
231
232 @Override
233 String getRepositoryUrl() {
234 return repoUrl;
235 }
236 };
237 final XmlGen oai = repo.selectVerb(p);
238 res.setCharacterEncoding("UTF-8");
239 res.setContentType("text/xml;charset=UTF-8");
240 oai.generate(res.getOutputStream());
241 }
242
243 private synchronized void tryUnregisterServlet() {
244 if (serviceRegistration != null) {
245 serviceRegistration.unregister();
246 serviceRegistration = null;
247 }
248 }
249
250
251
252
253
254
255
256
257
258 public static Optional<String> repositoryId(HttpServletRequest req, String mountPoint) {
259 String[] parts = StringUtils.removeStart(UrlSupport.removeDoubleSeparator(req.getRequestURI()), mountPoint).split("/");
260
261 return Arrays.stream(parts)
262 .flatMap(s -> trimToNil(s).stream())
263 .findFirst();
264 }
265
266
267 private Optional<OaiPmhRepository> getRepoById(String id) {
268 synchronized (repositories) {
269 if (hasRepo(id)) {
270 return Optional.of(repositories.get(id));
271 } else {
272 logger.warn("No OAI-PMH repository has been registered with id " + id);
273 return Optional.empty();
274 }
275 }
276 }
277
278 @Override
279 public boolean hasRepo(String id) {
280 synchronized (repositories) {
281 return repositories.containsKey(id);
282 }
283 }
284
285 @Override
286 public String getMountPoint() {
287 return mountPoint;
288 }
289 }