|
|
@@ -0,0 +1,475 @@
|
|
|
+package com.uas.report.axis.repository;
|
|
|
+
|
|
|
+import java.io.FileNotFoundException;
|
|
|
+import java.io.IOException;
|
|
|
+import java.io.StringReader;
|
|
|
+import java.io.StringWriter;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.Collections;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.Iterator;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Locale;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.Map.Entry;
|
|
|
+
|
|
|
+import javax.activation.DataHandler;
|
|
|
+import javax.activation.DataSource;
|
|
|
+
|
|
|
+import org.apache.axis.Message;
|
|
|
+import org.apache.axis.MessageContext;
|
|
|
+import org.apache.axis.attachments.Attachments;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.util.CollectionUtils;
|
|
|
+
|
|
|
+import com.uas.report.axis.Argument;
|
|
|
+import com.uas.report.axis.FileHelper;
|
|
|
+import com.uas.report.axis.FileResource;
|
|
|
+import com.uas.report.axis.Folder;
|
|
|
+import com.uas.report.axis.ListItem;
|
|
|
+import com.uas.report.axis.OperationResult;
|
|
|
+import com.uas.report.axis.Request;
|
|
|
+import com.uas.report.axis.Resource;
|
|
|
+import com.uas.report.axis.ResourceDescriptor;
|
|
|
+import com.uas.report.axis.ResultAttachments;
|
|
|
+import com.uas.report.axis.util.Marshaller;
|
|
|
+import com.uas.report.axis.util.Unmarshaller;
|
|
|
+
|
|
|
+@Service
|
|
|
+public class RepositoryManagementServiceImpl implements RepositoryManagementService {
|
|
|
+
|
|
|
+ private static final String VERSION = "2.0.1";
|
|
|
+
|
|
|
+ private Locale locale = null; // Default locale....
|
|
|
+
|
|
|
+ private Logger logger = LoggerFactory.getLogger(getClass());
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String list(String requestXmlString) {
|
|
|
+ OperationResult operationResult = new OperationResult();
|
|
|
+ operationResult.setVersion(VERSION);
|
|
|
+ try {
|
|
|
+ StringReader xmlStringReader = new StringReader(requestXmlString);
|
|
|
+ Request request = (Request) Unmarshaller.unmarshal(xmlStringReader);
|
|
|
+ setLocale(request.getLocale());
|
|
|
+
|
|
|
+ List<ResourceDescriptor> list = null;
|
|
|
+
|
|
|
+ if (request.getResourceDescriptor() == null) {
|
|
|
+ list = new ArrayList<>();
|
|
|
+ logger.debug("Null resourceDescriptor");
|
|
|
+ // Look for specific list requests...
|
|
|
+ if (getArgumentValue(Argument.LIST_DATASOURCES, request.getArguments()) != null
|
|
|
+ && getArgumentValue(Argument.LIST_DATASOURCES, request.getArguments())
|
|
|
+ .equals(Argument.VALUE_TRUE)) {
|
|
|
+ /*
|
|
|
+ * // List all datasources... FilterCriteria criteria =
|
|
|
+ * FilterCriteria.createFilter(ReportDataSource.class);
|
|
|
+ * logger.debug("Listing datasources...");
|
|
|
+ *
|
|
|
+ * // This filters with object level security // Will only
|
|
|
+ * get resources the user has access to
|
|
|
+ *
|
|
|
+ * List lookups = repository.loadClientResources(criteria);
|
|
|
+ * if (lookups != null && !lookups.isEmpty()) {
|
|
|
+ *
|
|
|
+ * for (Iterator it = lookups.iterator(); it.hasNext();) {
|
|
|
+ * list.add(createResourceDescriptor((Resource) it.next()));
|
|
|
+ * } }
|
|
|
+ */
|
|
|
+ } else if (request.hasArgument(Argument.LIST_RESOURCES)
|
|
|
+ && getArgumentValue(Argument.RESOURCE_TYPE, request.getArguments())
|
|
|
+ .equals(Argument.REPORT_TYPE)) {
|
|
|
+ /*
|
|
|
+ * // get list of reports from a certain directory
|
|
|
+ * recursively // List all datasources... logger.debug(
|
|
|
+ * "Listing all reports...");
|
|
|
+ *
|
|
|
+ * String parentFolder =
|
|
|
+ * getArgumentValue(Argument.START_FROM_DIRECTORY,
|
|
|
+ * request.getArguments()); if ((parentFolder == null) ||
|
|
|
+ * (!(parentFolder.startsWith("/")))) { parentFolder = "/";
|
|
|
+ * } // all options List allSubFolders =
|
|
|
+ * repository.getAllFolders(null); for (int i = 0; i <
|
|
|
+ * allSubFolders.size(); i++) { String currentFolder =
|
|
|
+ * ((Folder) allSubFolders.get(i)).getURIString();
|
|
|
+ * FilterCriteria filterCriteria = new FilterCriteria();
|
|
|
+ * filterCriteria.addFilterElement(FilterCriteria.
|
|
|
+ * createParentFolderFilter(currentFolder)); List units =
|
|
|
+ * repository.loadClientResources(filterCriteria); for (int
|
|
|
+ * j = 0; j < units.size(); j++) { Resource currentRs =
|
|
|
+ * (Resource) units.get(j); if ((currentRs.getResourceType()
|
|
|
+ * != null) &&
|
|
|
+ * ((currentRs.getResourceType().contains("ReportOptions"))
|
|
|
+ * || (currentRs.getResourceType().contains("ReportUnit"))
|
|
|
+ * ||
|
|
|
+ * (currentRs.getResourceType().contains("AdhocReportUnit"))
|
|
|
+ * )) {
|
|
|
+ *
|
|
|
+ * String temp = serviceConfiguration.getTempFolder(); if
|
|
|
+ * ("/".equals(parentFolder)) { if
|
|
|
+ * ((!(ADHOC_TOPICS.equals(currentRs.getParentFolder()))) &&
|
|
|
+ * (!(temp.equals(currentRs.getParentFolder())))) {
|
|
|
+ * list.add(createResourceDescriptor(currentRs)); } } else
|
|
|
+ * if ((currentRs.getURIString() +
|
|
|
+ * "/").startsWith(parentFolder + "/")) { if
|
|
|
+ * ((!(ADHOC_TOPICS.equals(currentRs.getParentFolder()))) &&
|
|
|
+ * (!(temp.equals(currentRs.getParentFolder())))) {
|
|
|
+ * list.add(createResourceDescriptor(currentRs)); } } } } }
|
|
|
+ */
|
|
|
+ } else if (request.hasArgument(Argument.LIST_RESOURCES)) {
|
|
|
+ /*
|
|
|
+ * String resourceType =
|
|
|
+ * request.getArgumentValue(Argument.RESOURCE_TYPE);
|
|
|
+ *
|
|
|
+ * if (resourceType == null) { if (logger.isDebugEnabled())
|
|
|
+ * { logger.debug("No " + Argument.RESOURCE_TYPE +
|
|
|
+ * " argument, nothing to list"); } } else { ResourceHandler
|
|
|
+ * handler = handlerRegistry.getHandler(resourceType); if
|
|
|
+ * (handler == null) { throw new JSException(
|
|
|
+ * "No resource hander found for type " + resourceType); }
|
|
|
+ *
|
|
|
+ * List resources = handler.listResources(request, this); if
|
|
|
+ * (resources != null) { list.addAll(resources); } }
|
|
|
+ */
|
|
|
+ }
|
|
|
+ } else if (request.getResourceDescriptor().getWsType().equals(ResourceDescriptor.TYPE_FOLDER)) {
|
|
|
+ logger.debug("List folders");
|
|
|
+ list = listResources(request.getResourceDescriptor().getUriString());
|
|
|
+ } else if (request.getResourceDescriptor().getWsType().equals(ResourceDescriptor.TYPE_REPORTUNIT)) {
|
|
|
+ /*
|
|
|
+ * logger.debug("List report units"); list =
|
|
|
+ * createResourceDescriptor(request.getResourceDescriptor().
|
|
|
+ * getUriString()).getChildren();
|
|
|
+ */
|
|
|
+ } else {
|
|
|
+ logger.debug("Listed nothing");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (logger.isDebugEnabled()) {
|
|
|
+ logger.debug("Found " + list.size() + " things");
|
|
|
+ for (Iterator<ResourceDescriptor> it = list.iterator(); it.hasNext();) {
|
|
|
+ ResourceDescriptor rd = it.next();
|
|
|
+ logger.debug(rd != null ? rd.getName() : "rd was null");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ operationResult.setResourceDescriptors(list);
|
|
|
+
|
|
|
+ logger.debug("Marshalling response");
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ logger.error("caught exception: " + e.getMessage(), e);
|
|
|
+ operationResult.setReturnCode(1);
|
|
|
+ operationResult.setMessage(e.getMessage());
|
|
|
+ } catch (Throwable e) {
|
|
|
+ logger.error("caught exception: " + e.getMessage(), e);
|
|
|
+ }
|
|
|
+
|
|
|
+ return marshalResponse(operationResult);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String get(String requestXmlString) {
|
|
|
+ OperationResult operationResult = new OperationResult();
|
|
|
+ operationResult.setVersion(VERSION);
|
|
|
+ try {
|
|
|
+ StringReader xmlStringReader = new StringReader(requestXmlString);
|
|
|
+ Request request = (Request) Unmarshaller.unmarshal(xmlStringReader);
|
|
|
+ // createAuditEvent(request.getOperationName(),
|
|
|
+ // request.getResourceDescriptor().getWsType(),
|
|
|
+ // request.getResourceDescriptor().isNew());
|
|
|
+ setLocale(request.getLocale());
|
|
|
+
|
|
|
+ List<Argument> args = request.getArguments();
|
|
|
+
|
|
|
+ List<ListItem> params = Collections.emptyList();
|
|
|
+ if (request.getResourceDescriptor() != null && request.getResourceDescriptor().getParameters() != null) {
|
|
|
+ params = request.getResourceDescriptor().getParameters();
|
|
|
+ }
|
|
|
+
|
|
|
+ HashMap<String, String> specialOptions = new HashMap<>();
|
|
|
+ if (args != null) {
|
|
|
+ for (int i = 0; i < args.size(); ++i) {
|
|
|
+ Argument arg = args.get(i);
|
|
|
+ if (arg != null) {
|
|
|
+ specialOptions.put(arg.getName(), arg.getValue());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+// if (params.size() > 0 && specialOptions.containsKey(Argument.RU_REF_URI)) {
|
|
|
+// ResourceDescriptor reportDescriptior = createResourceDescriptor(
|
|
|
+// (String) specialOptions.get(Argument.RU_REF_URI));
|
|
|
+// specialOptions.put(Argument.PARAMS_ARG, buildParameterMap(params, reportDescriptior));
|
|
|
+// }
|
|
|
+
|
|
|
+ String resourceURI = request.getResourceDescriptor().getUriString();
|
|
|
+ Resource resource = FileHelper.locateResource(resourceURI);
|
|
|
+ if (resource == null) {
|
|
|
+ logger.warn("Get: null resourceDescriptor for " + resourceURI);
|
|
|
+ operationResult.setReturnCode(2);
|
|
|
+// operationResult
|
|
|
+// .setMessage(messageSource.getMessage("webservices.error.resourceNotFound", null, getLocale()));
|
|
|
+ } else {
|
|
|
+// ResourceDescriptor rd = createResourceDescriptor(resource, processDescriptorOptions(specialOptions));
|
|
|
+ ResourceDescriptor rd = createResourceDescriptor(resource);
|
|
|
+
|
|
|
+ logger.debug("Get: " + resourceURI + ", wsType: " + rd.getWsType() + ", resourceType: "
|
|
|
+ + rd.getResourceType());
|
|
|
+ operationResult.getResourceDescriptors().add(rd);
|
|
|
+
|
|
|
+ ResultAttachments attachments = new ResultAttachments();
|
|
|
+ attachments
|
|
|
+ .setEncapsulationDime(getArgumentValue("USE_DIME_ATTACHMENTS", request.getArguments()) != null);
|
|
|
+// ResourceHandler handler = getHandlerRegistry().getHandler(rd.getWsType());
|
|
|
+// handler.getAttachments(resource, specialOptions, rd, attachments, this);
|
|
|
+// if (operationResult.getReturnCode() != 0) {
|
|
|
+// addExceptionToAllAuditEvents(new Exception(operationResult.getMessage()));
|
|
|
+// }
|
|
|
+//
|
|
|
+// return marshalResponse(operationResult, attachments);
|
|
|
+ return marshalResponse(operationResult);
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+
|
|
|
+ logger.error("caught exception: " + e.getMessage(), e);
|
|
|
+ operationResult.setReturnCode(1);
|
|
|
+ operationResult.setMessage(e.getMessage());
|
|
|
+ // addExceptionToAllAuditEvents(e);
|
|
|
+ }
|
|
|
+ logger.debug("Marshalling response");
|
|
|
+
|
|
|
+ return marshalResponse(operationResult);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String put(String requestXmlString) {
|
|
|
+ // TODO Auto-generated method stub
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String delete(String requestXmlString) {
|
|
|
+ // TODO Auto-generated method stub
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String runReport(String requestXmlString) {
|
|
|
+ // TODO Auto-generated method stub
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String move(String requestXmlString) {
|
|
|
+ // TODO Auto-generated method stub
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String copy(String requestXmlString) {
|
|
|
+ // TODO Auto-generated method stub
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ public Locale getLocale() {
|
|
|
+ return locale;
|
|
|
+ }
|
|
|
+
|
|
|
+ // TODO fix
|
|
|
+ public void setLocale(Locale locale) {
|
|
|
+ this.locale = locale;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * This method takes the Locale requested by the client. If the requested
|
|
|
+ * locale is null, the default locale is returned.
|
|
|
+ *
|
|
|
+ * A locale code can be in the form:
|
|
|
+ *
|
|
|
+ * langagecode[_countrycode]
|
|
|
+ *
|
|
|
+ * Ex: en_US, it_IT, it, en_UK
|
|
|
+ *
|
|
|
+ */
|
|
|
+ private void setLocale(String requestedLocale) {
|
|
|
+ try {
|
|
|
+ if (requestedLocale != null) {
|
|
|
+ String language = requestedLocale;
|
|
|
+ String country = "";
|
|
|
+ if (requestedLocale.indexOf("_") > 0) {
|
|
|
+ language = requestedLocale.substring(0, requestedLocale.indexOf("_"));
|
|
|
+ country = requestedLocale.substring(requestedLocale.indexOf("_") + 1);
|
|
|
+ setLocale(new Locale(language, country));
|
|
|
+ } else {
|
|
|
+ setLocale(new Locale(language));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception ex) {
|
|
|
+ logger.error("Unable to get requested locale (" + requestedLocale + ")");
|
|
|
+ setLocale(Locale.getDefault());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private String marshalResponse(OperationResult operationResult) {
|
|
|
+ return marshalResponse(operationResult, new HashMap<String, DataSource>(), false);
|
|
|
+ }
|
|
|
+
|
|
|
+ private String marshalResponse(OperationResult operationResult, Map<String, DataSource> datasources,
|
|
|
+ boolean isEncapsulationDime) {
|
|
|
+
|
|
|
+ String result = "";
|
|
|
+
|
|
|
+ // First of all attach the attachments...
|
|
|
+ if (datasources != null) {
|
|
|
+ MessageContext msgContext = MessageContext.getCurrentContext();
|
|
|
+ Message responseMessage = msgContext.getResponseMessage();
|
|
|
+
|
|
|
+ logger.debug("Encapsulation DIME? : " + isEncapsulationDime);
|
|
|
+
|
|
|
+ if (isEncapsulationDime) {
|
|
|
+ responseMessage.getAttachmentsImpl().setSendType(Attachments.SEND_TYPE_DIME);
|
|
|
+ }
|
|
|
+
|
|
|
+ for (Iterator<Entry<String, DataSource>> it = datasources.entrySet().iterator(); it.hasNext();) {
|
|
|
+ try {
|
|
|
+ Entry<String, DataSource> entry = it.next();
|
|
|
+ String name = (String) entry.getKey();
|
|
|
+ DataSource datasource = (DataSource) entry.getValue();
|
|
|
+
|
|
|
+ logger.debug("Adding attachment: " + name + ", type: " + datasource.getContentType());
|
|
|
+
|
|
|
+ DataHandler expectedDH = new DataHandler(datasource);
|
|
|
+
|
|
|
+ javax.xml.soap.AttachmentPart attachPart = null;
|
|
|
+ attachPart = responseMessage.createAttachmentPart(expectedDH);
|
|
|
+
|
|
|
+ // javax.xml.soap.AttachmentPart ap2 =
|
|
|
+ // responseMessage.createAttachmentPart();
|
|
|
+ // ap2.setContent( datasource.getInputStream(),
|
|
|
+ // datasource.getContentType());
|
|
|
+ attachPart.setContentId(name);
|
|
|
+ responseMessage.addAttachmentPart(attachPart);
|
|
|
+
|
|
|
+ } catch (Exception ex) {
|
|
|
+ logger.error("caught exception marshalling an OperationResult: " + ex.getMessage(), ex);
|
|
|
+ // What to do?
|
|
|
+ operationResult.setReturnCode(1);
|
|
|
+ operationResult.setMessage("Error attaching a resource to the SOAP message: " + ex.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ StringWriter xmlStringWriter = new StringWriter();
|
|
|
+ Marshaller.marshal(operationResult, xmlStringWriter);
|
|
|
+ if (logger.isDebugEnabled()) {
|
|
|
+ logger.debug("Has descriptors: " + ((operationResult.getResourceDescriptors() == null
|
|
|
+ || operationResult.getResourceDescriptors().size() == 0) ? 0
|
|
|
+ : operationResult.getResourceDescriptors().size()));
|
|
|
+ logger.debug("marshalled response");
|
|
|
+ logger.debug(xmlStringWriter.toString());
|
|
|
+ }
|
|
|
+ result = xmlStringWriter.toString();
|
|
|
+
|
|
|
+ } catch (Exception ex) {
|
|
|
+ logger.error("caught exception marshalling an OperationResult: " + ex.getMessage(), ex);
|
|
|
+ // What to do?
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected String getArgumentValue(String argumentName, List<Argument> arguments) {
|
|
|
+ for (int i = 0; i < arguments.size(); ++i) {
|
|
|
+ Argument a = (Argument) arguments.get(i);
|
|
|
+ if (a.getName() == null ? a.getName() == argumentName : a.getName().equals(argumentName)) {
|
|
|
+ return a.getValue();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Return a list of ResourceDescriptor(s)
|
|
|
+ * @throws IOException
|
|
|
+ * @throws FileNotFoundException
|
|
|
+ *
|
|
|
+ * @throws WSException
|
|
|
+ */
|
|
|
+ public List<ResourceDescriptor> listResources(String uri) throws FileNotFoundException, IOException {
|
|
|
+ logger.debug("list for uri: " + uri);
|
|
|
+
|
|
|
+ List<ResourceDescriptor> returnedMaps = new ArrayList<>();
|
|
|
+
|
|
|
+ List<Resource> resources = FileHelper.listResource(uri);
|
|
|
+ if(CollectionUtils.isEmpty(resources)){
|
|
|
+ return returnedMaps;
|
|
|
+ }
|
|
|
+ for(Resource resource :resources){
|
|
|
+ returnedMaps.add(createResourceDescriptor(resource));
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * // This filters with object level security. // Will only get folders
|
|
|
+ * the user has access to
|
|
|
+ *
|
|
|
+ * List<Resource> folders = getRepository().getSubFolders(null, uri);
|
|
|
+ * filterFolderList(folders);
|
|
|
+ *
|
|
|
+ * if (folders == null) return returnedMaps;
|
|
|
+ *
|
|
|
+ * for (int i = 0; i < folders.size(); ++i) { Resource folderRes =
|
|
|
+ * folders.get(i);
|
|
|
+ * returnedMaps.add(createResourceDescriptor(folderRes)); }
|
|
|
+ *
|
|
|
+ * // create a criteria for finding things with a common parent folder.
|
|
|
+ * FilterCriteria filterCriteria = new FilterCriteria();
|
|
|
+ * filterCriteria.addFilterElement(FilterCriteria.
|
|
|
+ * createParentFolderFilter(uri));
|
|
|
+ *
|
|
|
+ * // This filters with object level security // Will only get resources
|
|
|
+ * the user has access to
|
|
|
+ *
|
|
|
+ * List units = getRepository().loadClientResources(filterCriteria);
|
|
|
+ *
|
|
|
+ * if (units == null) return returnedMaps;
|
|
|
+ *
|
|
|
+ * for (Iterator it = units.iterator(); units != null && it.hasNext();)
|
|
|
+ * { Resource fileRes = (Resource) it.next(); try {
|
|
|
+ * returnedMaps.add(createResourceDescriptor(fileRes)); } catch
|
|
|
+ * (Exception ex) { logger.error(ex); } }
|
|
|
+ */
|
|
|
+
|
|
|
+ return returnedMaps;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * the same as createResourceDescriptor( resource, false)
|
|
|
+ */
|
|
|
+ public ResourceDescriptor createResourceDescriptor(Resource resource) {
|
|
|
+ ResourceDescriptor descriptor = new ResourceDescriptor();
|
|
|
+ if(resource instanceof Folder){
|
|
|
+ descriptor.setWsType(ResourceDescriptor.TYPE_FOLDER);
|
|
|
+ descriptor.setHasData(false);
|
|
|
+ }else if(resource instanceof FileResource){
|
|
|
+ FileResource fileResource=(FileResource)resource;
|
|
|
+ descriptor.setWsType(fileResource.getFileType());
|
|
|
+ descriptor.setHasData(fileResource.hasData());
|
|
|
+ }
|
|
|
+ descriptor.setUriString(resource.getURIString());
|
|
|
+ descriptor.setDescription(resource.getDescription());
|
|
|
+ descriptor.setLabel(resource.getLabel());
|
|
|
+ descriptor.setName(resource.getName());
|
|
|
+ descriptor.setResourceType(resource.getResourceType());
|
|
|
+ descriptor.setParentFolder(resource.getParentFolder());
|
|
|
+ descriptor.setVersion(resource.getVersion());
|
|
|
+ descriptor.setCreationDate(resource.getCreationDate());
|
|
|
+ return descriptor;
|
|
|
+ }
|
|
|
+
|
|
|
+}
|