Spring boot and Swagger url and startup questions - spring-boot

I come from programming in c# and now I have to create a couple of Rest Apis in Spring Boot.
Everything is working ok and I can show the API in Swagger with springfox-swagger-ui
But I have two questions that I could not find in Internet
Is there any way to show the url ui in the console app with server, port, etc?
Is there any way to open the swagger url everytime I run the app in the localhost?
Thanks
Spring boot version
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>3.0.0</version>
</dependency>

I know it's not the question you're asking, but springfox is currently having issues with newer versions of spring. The spring version you're using is still working but as of 2.6 there are bugs, and it looks like the project is not well maintained. Since you're at the beginning of the project, switching is not too hard. You could move to springdocs for example (for migration: https://springdoc.org/#migrating-from-springfox).
With respect to opening a url, there are some good solutions mentioned here: How to open the default webbrowser using java . You could make your swagger url a property and have swagger configure it accordingly, then you can reuse the property to call the url on run-time. If you want to differentiate between environments I'd suggest use profiles. Only open the url in the browser if you start the app on dev environment, and not on prod is then specified by using #Profile("dev"). Create a commandline/application runner with the profile annotation (https://www.tutorialspoint.com/spring_boot/spring_boot_runners.htm), and call the url from there.
That said, combining it gives:
package com.example.demo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
import java.awt.*;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
#Profile("dev")
#Component
public class SwaggerRunner implements ApplicationRunner {
#Value("${springdoc.swagger-ui.path}")
private String swaggerPath;
#Override
public void run(ApplicationArguments args) throws Exception {
log("\nWelcome to Multi Brow Pop.\nThis aims to popup a browsers in multiple operating systems.\nGood luck!\n");
final String swaggerUrl = "http://localhost:8000/" + swaggerPath;
log("We're going to this page: " + swaggerUrl);
String myOS = System.getProperty("os.name").toLowerCase();
log("(Your operating system is: " + myOS + ")\n");
try {
if (Desktop.isDesktopSupported()) { // Probably Windows
log(" -- Going with Desktop.browse ...");
Desktop desktop = Desktop.getDesktop();
desktop.browse(new URI(swaggerUrl));
} else { // Definitely Non-windows
Runtime runtime = Runtime.getRuntime();
if (myOS.contains("mac")) { // Apples
log(" -- Going on Apple with 'open'...");
runtime.exec("open " + swaggerUrl);
} else if (myOS.contains("nix") || myOS.contains("nux")) { // Linux flavours
log(" -- Going on Linux with 'xdg-open'...");
runtime.exec("xdg-open " + swaggerUrl);
} else
log("I was unable/unwilling to launch a browser in your OS :( #SadFace");
}
log("\nThings have finished.\nI hope you're OK.");
} catch (IOException | URISyntaxException eek) {
log("**Stuff wrongly: " + eek.getMessage());
}
}
private static void log(String log) {
System.out.println(log);
}
}
put springdoc.swagger-ui.path=/custom/path in your application.properties to change the path to your swagger-ui

Related

Spring and Azure function

Does Spring work with Azure functions?
For example: Rest API that the code inside uses "Autowired" annotation (After running mvn azure-functions:run I've got NullPointerException on "myScriptService").
import java.util.*;
import com.microsoft.azure.serverless.functions.annotation.*;
import com.microsoft.azure.serverless.functions.*;
import com.company.ScriptService;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Azure Functions with HTTP Trigger.
*/
public class Function {
#Autowired
ScriptService myScriptService;
/**
* This function listens at endpoint "/api/hello". Two ways to invoke it using "curl" command in bash:
* 1. curl -d "HTTP Body" {your host}/api/hello
* 2. curl {your host}/api/hello?name=HTTP%20Query
*/
#FunctionName("myhello")
public HttpResponseMessage<String> hello(
#HttpTrigger(name = "req",
methods = "post",
authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> request,
final ExecutionContext context) {
context.getLogger().info("Java HTTP trigger processed a request.");
// Parse query parameter
String query = request.getQueryParameters().get("name");
String name = request.getBody().orElse(query);
if (name == null) {
return request.createResponse(400, "Please pass a name on the query string or in the request body");
} else {
return request.createResponse(200, "Hello, " + name + ", myScriptService.isEnabled(): " + myScriptService.isEnabled());
}
}
}
As some asked for a solution in the comments above, I'm assuming that this problem might be of relevance for other users, too.
So I think Spring Cloud Function is the magic word here: besides some other points (see the project page for details), it aims to enable Spring Boot features (like dependency injection, what you're looking for) on serverless providers (besides Azure Functions, also AWS Lambda and Apache OpenWhisk are supported).
So you have to make some modifications to your project:
Add the spring-cloud-function-adapter-azure dependency:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-adapter-azure</artifactId>
<version>2.0.1.RELEASE</version>
</dependency>
Your handler class needs some additional code:
Add the #SpringBootApplication annotation
Add the main() method known from Spring Boot applications
Make sure that Spring can find your ScriptService class e. g. by using the #ComponentScan annotation
It should look like this:
#SpringBootApplication
#ComponentScan(basePackages = { "package.of.scriptservice" })
public class Function {
#Autowired
ScriptService myScriptService;
#FunctionName("myhello")
public HttpResponseMessage<String> hello(
#HttpTrigger(name = "req", methods = "post", authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> request,
final ExecutionContext context) {
// Your code here
}
public static void main(String[] args) {
SpringApplication.run(DemoFunctionHandler.class, args);
}
}
You can find a full example here and here
It looks like that there are a lot of changes between spring cloud v1 and v2. Have a quick look at this blog post: https://spring.io/blog/2018/09/25/spring-cloud-function-2-0-and-azure-functions
If you build your project like the example, spring will create the spring boot context when the azure function is called (and you call handleRequest). But the spring context is not available before this.
Do you add your package to scan for spring cloud function ?
spring.cloud.function.scan.packages="yourPackage"
It is to add in your application.properties

How to set ENCRYPTION_AES_128? - iText 7.1.0 for Java

At the moment I'm writing a couple of evaluatuation programs with iText.
I have an issue with AES Encryption.
STANDARD_ENCRYPTION_128 is working fine but ENCRYPTION_AES_128 produces a runtime error.
I tried a lot but nothing worked. Has anybody a clue what's wrong here?
Thanks, Dirk
import java.awt.Desktop;
import java.io.File;
import java.io.IOException;
import com.itextpdf.kernel.pdf.EncryptionConstants;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.WriterProperties;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.Paragraph;
public class problem3 {
public static void main(String[] args) throws IOException {
String fnPdf = "results/problem3.pdf";
WriterProperties properties = new WriterProperties();
// ENCRYPTION_AES_128 produces an runtime error, STANDARD_ENCRYPTION_128 is working.
properties.setStandardEncryption("Hello".getBytes(), "World".getBytes(), EncryptionConstants.ALLOW_PRINTING,
EncryptionConstants.ENCRYPTION_AES_128);
PdfWriter writer = new PdfWriter(fnPdf, properties);
PdfDocument pdf = new PdfDocument(writer);
Document document = new Document(pdf);
Paragraph paragraph = new Paragraph("Hello AES-128!");
document.add(paragraph);
document.close();
pdf.close();
Desktop.getDesktop().open(new File(fnPdf));
}
}
Your error at runtime is probably
java.lang.NoClassDefFoundError: org/bouncycastle/crypto/BlockCipher
This is because iText uses BouncyCastle library for providing some of the encryption capabilities. The dependency is optional which means you have to add it manually if you need it.
If you use Maven for building, then make sure that you have the following dependencies:
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.49</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.49</version>
</dependency>
If you are adding jars to the classpath manually, which is not recommended, then you can go to Maven Central and download the necessary artifact jars manually.

Declarative services in OSGI

I have created a (very) simple test to determine how to send and receive events using Apache Felix.
This is my sender:
package be.pxl;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;
import java.util.HashMap;
#Component(name = "be.pxl.Publisher", immediate = true)
public class Publisher {
EventAdmin admin;
#Activate
public void run(Object object) {
System.out.println("IN PUBLISHER");
Event event = new Event("event", new HashMap<String, Object>());
System.out.println("\tEVENT: " + event);
admin.postEvent(event);
System.out.println("\tADMIN: " + admin);
}
#Reference(name="be.pxl.admin", service = EventAdmin.class)
protected void setEventAdmin(EventAdmin admin) {
this.admin = admin;
}
}
This is my receiver:
package be.pxl;
import org.osgi.framework.BundleContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventConstants;
import org.osgi.service.event.EventHandler;
import java.util.Dictionary;
import java.util.Hashtable;
#Component(name = "be.pxl.Subscriber", immediate = true)
public class Subscriber implements EventHandler {
private BundleContext context;
#Activate
public void run(Object object) {
System.out.println("IN SUBSCRIBER");
System.out.println("\tIN RUN METHOD");
String[] topics = new String[]{"event"};
Dictionary props = new Hashtable();
props.put(EventConstants.EVENT_TOPIC, topics);
System.out.println("\t\tCONTEXT: " + context);
context.registerService(EventHandler.class.getName(), this, props);
System.out.println("\t\tCONTEXT AFTER REGISTERSERVICE: " + context);
}
public void handleEvent(Event event) {
System.out.println("IN SUBSCRIBER");
String text = event.getProperty("text").toString();
System.out.println("\tEVENT CALLED: " + text);
}
#Reference(name="be.pxl.context", service=BundleContext.class)
protected void setBundleContex(BundleContext context) {
this.context = context;
}
}
This is the pom of my sender:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>be.pxl</groupId>
<artifactId>EventSender</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.service.event</artifactId>
<version>1.3.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.core</artifactId>
<version>6.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.service.component.annotations</artifactId>
<version>1.3.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.osgi</groupId>
<artifactId>org.eclipse.osgi.services</artifactId>
<version>3.2.100.v20100503</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>2.4.0</version>
<extensions>true</extensions>
<configuration>
<instructions>
<Bundle-Vendor>SmartCampus</Bundle-Vendor>
<Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
<Export-Package>
be.pxl.*;version="1.0.0"
</Export-Package>
<Import-Package>
org.osgi.service.component.annotations
org.eclipse.osgi.service
org.osgi.core
org.osgi.service.event
</Import-Package>
<_dsannotations>*</_dsannotations>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
Everything compiles fine. I create it using mvn clean package, then I install this jar file in my apache felix container and start it. However, nothing happens. Nothing get pritns out.
Thanks in advance!
You appear to be most of the way there! As you've identified, Event Admin uses a whiteboard model to receive events. The important thing is that you need to tell the whiteboard which topics you want to listen to, which you do.
%%% Update %%%
Event admin topic names use a hierarchy of tokens separated by / characters. When publishing an event you do so to a specific topic, for example foo/bar/baz. When receiving events the EventHandler will be called for topics that match its registered interest(s). These interests can either be for a specific topic, or they can end with a * to indicate a wildcard match. For example foo/bar/* would receive events sent to foo/bar/baz and events sent to foo/bar/fizzbuzz.
%%% Back to the original %%%
There are, however a couple of issues with your code:
Firstly:
#Reference(name="be.pxl.context", service=BundleContext.class)
protected void setBundleContex(BundleContext context) {
this.context = context;
}
This is not how you access the BundleContext for your bundle. If you do need a BundleContext then it should be injected as a parameter into your #Activate annotated method. A BundleContext should never be registered as a service (it represents your bundle's private access to the OSGi framework), and it would not surprise me to find that this reference is unsatisfied in your example. You don't actually need the BundleContext however because...
Secondly:
#Activate
public void run(Object object) {
System.out.println("IN SUBSCRIBER");
System.out.println("\tIN RUN METHOD");
String[] topics = new String[]{"event"};
Dictionary props = new Hashtable();
props.put(EventConstants.EVENT_TOPIC, topics);
System.out.println("\t\tCONTEXT: " + context);
context.registerService(EventHandler.class.getName(), this, props);
System.out.println("\t\tCONTEXT AFTER REGISTERSERVICE: " + context);
}
This is not the right way to write an activate method (and as a result it may not be being called), nor should you be registering your component as a service here. When you make your class an #Component it will automatically be registered as a service using each directly implemented interface. This means that:
#Component(name = "be.pxl.Subscriber", immediate = true)
public class Subscriber implements EventHandler {
...
}
is already an OSGi EventHandler service!
You can add service properties to your component using the #Component annotation, or from the OSGi R7 release (due in a couple of months) using Component Property annotations. In this case you want to set your event.topics property like this:
#Component(property="event.topics=event")
You can then get rid of the activate method completely if you like.
Finally:
Event Admin is not a message queue, and your publisher is a one-shot send. Therefore if your publisher sends the event before the handler is fully registered then it will never receive the event. Consider making the publisher send periodic events, or be certain that the receiver starts before the publisher so that you see the message.
P.S.
It's not technically a problem, but I see that you're using version 2.4 of the maven-bundle-plugin. This is very old and the current released version of bnd is 3.5.0. The Bnd team have also started providing their own Maven plugins (such as the bnd-maven-plugin) that you might want to look at.

Microservice with Spring Boot

I'm working in Windows 7. I've Spring CLI v1.5.3.RELEASE installed. In a working directory, using command
spring init --build maven --groupId com.redhat.examples
--version 1.0 --java-version 1.8 --dependencies web
--name hola-springboot hola-springboot
I created holo-springboot app. Then navigated to hola-springboot directory,ran
$ mvn spring-boot:run
The application run. Going to http://localhost:8080, I do see Whitelabel error page. Whereafter, I tried to add helloworld fuctionality. That is, in the app, in the packeage com.example, I included the following java class.
#RestController
#RequestMapping("/api")
public class HolaRestController {
#RequestMapping(method = RequestMethod.GET, value = "/hola",
produces = "text/plain")
public String hola() throws UnknownHostException {
String hostname = null;
try {
hostname = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
hostname = "unknown";
}
return "Hola Spring Boot de " + hostname;
}
}
Re-built from hola-springboot dircetory,
mvn clean package
I get build failure as at
https://pastebin.com/77Ru0w52
I'm unable to figure out. Could somebody help?
I'm following the book Microservices for Java Developers by Christian Posta, Chapter 2, available free at developers Redhat.
Looks like you are missing a dependency on spring boot starter web in your maven pom.xml file https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web/1.5.3.RELEASE.
Or you are not importing the classes correctly.
You are accessing http://localhost:8080 but you have defined a mapping in your rest controller "/hola". So you will have to access the url http://localhost:8080/hola as you do not have any default method in your rest controller.
BuildFailure shows that you have not given import statements in you Class. statements missing are the below
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.net.InetAddress;
import java.net.UnknownHostException;
include these and you will be fine.

How to make webjars version agnostic in spring mvc

I have followed the documentation on their website as described here
First of all I added the required path
<mvc:resources mapping="/webjars/**" location="/webjars/"/>
then I created a controller with the following
#ResponseBody
#RequestMapping("/webjarslocator/{webjar}/**")
public ResponseEntity locateWebjarAsset(#PathVariable String webjar, HttpServletRequest request) {
try {
String mvcPrefix = "/webjarslocator/" + webjar + "/"; // This prefix must match the mapping path!
String mvcPath = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
String fullPath = assetLocator.getFullPath(webjar, mvcPath.substring(mvcPrefix.length()));
return new ResponseEntity(new ClassPathResource(fullPath), HttpStatus.OK);
} catch (Exception e) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
Few dependencies were missing so I added in maven the following pom
<dependency>
<groupId>org.webjars</groupId>
<artifactId>webjars-locator</artifactId>
<version>0.28</version>
</dependency>
The above will import the following
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.core.io.ClassPathResource;
None of these has been imported from the external jar.
The error is: assetLocator cannot be resolved
EDIT: It could be that I need to create a filter rather than put it in a controller. Any thoughts on this?
The documentation is quite sparse, but you can create an instance of an asset locator with new WebJarAssetLocator().
Here's another solution, considering more recent versions of WebJar, in a similar vein for anyone else who stumbles upon this question. Using org.webjars.play.WebJarsUtil#locate(java.lang.String, java.lang.String) (which, in-turn, uses org.webjars.WebJarAssetLocator#getFullPath(java.lang.String, java.lang.String)) accepts the WebJar name (which acts as a scope) and the file without needing to provide a full path that necessitates including the version.

Resources