Esta aplicación no tiene un mapeo explícito para / error


108

Usé maven para hacer el tutorial https://spring.io/guides/gs/uploading-files/
Se copiaron todos los códigos que usé.

La aplicación se puede ejecutar, pero aparece el error:

Página de error de etiqueta blanca Esta aplicación no tiene un mapeo explícito para / error, por lo que está viendo esto como una alternativa. Mar 30 de junio 17:24:02 CST 2015 Hubo un error inesperado (tipo = No encontrado, estado = 404). No hay mensajes disponibles

¿Cómo puedo arreglarlo?


agregó su comentario a la publicación; puede editarlo usted mismo. Eso es mejor que comentar su propia publicación
Alexander

Respuestas:


137

Asegúrese de que su clase principal esté en un paquete raíz por encima de otras clases.

Cuando ejecuta una aplicación Spring Boot (es decir, una clase anotada con @SpringBootApplication), Spring solo escaneará las clases debajo de su paquete de clases principal.

com
   +- APP
         +- Application.java  <--- your main class should be here, above your controller classes
         |
         +- model
         |   +- user.java
         +- controller
             +- UserController.java

4
¿Arriba o al mismo nivel?
Martin Erlic

21
¡Pasé casi 2 horas de mi vida resolviendo esto!
Rakesh

7
Probé eso también. Todavía error. Al menos la página principal, es decir, localhost: 8080 debería mostrarme la página de inicio de Tomcat, ¿no es así? Pero eso tampoco se
nota

Gracias por la pista. Solía ​​ser un usuario de Eclipse y allí no se necesitaba esta configuración, pero ahora estoy usando IntelliJ y era muy esperanzador.
Armer B.

@zulkarnainshah La página de inicio habitual de Tomcat se genera mediante un WAR que no se incluye aquí.
Thorbjørn Ravn Andersen

61

Cuando creamos una aplicación de arranque Spring, la anotamos con una @SpringBootApplicationanotación. Esta anotación 'resume' muchas otras anotaciones necesarias para que la aplicación funcione. Una de esas anotaciones es la @ComponentScananotación. Esta anotación le dice a Spring que busque componentes de Spring y configure la aplicación para que se ejecute.

Su clase de aplicación debe estar en la parte superior de la jerarquía de paquetes, para que Spring pueda escanear subpaquetes y descubrir los otros componentes necesarios.

package com.test.spring.boot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}

El siguiente fragmento de código funciona ya que el paquete del controlador está debajo del com.test.spring.bootpaquete

package com.test.spring.boot.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HomeController {

    @RequestMapping("/")
    public String home(){
        return "Hello World!";
    }
}

El siguiente fragmento de código NO funciona ya que el paquete del controlador NO está debajo del com.test.spring.bootpaquete

package com.test.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HomeController {

     @RequestMapping("/")
     public String home(){
         return "Hello World!";
     }
 }

De la documentación de Spring Boot:

Muchos desarrolladores de Primavera de arranque siempre tienen su clase principal anotado con @Configuration, @EnableAutoConfigurationy @ComponentScan. Dado que estas anotaciones se usan juntas con tanta frecuencia (especialmente si sigue las mejores prácticas anteriores), Spring Boot proporciona una @SpringBootApplicationalternativa conveniente .

La @SpringBootApplicationanotación es equivalente a usar @Configuration, @EnableAutoConfigurationy @ComponentScancon sus atributos predeterminados


3
Muy bonita explicación. Gracias
Lova Chittumuri

39

Puede resolver esto agregando un ErrorControlleren su aplicación. Puede hacer que el controlador de errores devuelva una vista que necesita.

El controlador de error en mi aplicación se ve a continuación:

import org.springframework.boot.autoconfigure.web.ErrorAttributes;
import org.springframework.boot.autoconfigure.web.ErrorController;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;

/**
 * Basic Controller which is called for unhandled errors
 */
@Controller
public class AppErrorController implements ErrorController{

    /**
     * Error Attributes in the Application
     */
    private ErrorAttributes errorAttributes;

    private final static String ERROR_PATH = "/error";

    /**
     * Controller for the Error Controller
     * @param errorAttributes
     */
    public AppErrorController(ErrorAttributes errorAttributes) {
        this.errorAttributes = errorAttributes;
    }

    /**
     * Supports the HTML Error View
     * @param request
     * @return
     */
    @RequestMapping(value = ERROR_PATH, produces = "text/html")
    public ModelAndView errorHtml(HttpServletRequest request) {
        return new ModelAndView("/errors/error", getErrorAttributes(request, false));
    }

    /**
     * Supports other formats like JSON, XML
     * @param request
     * @return
     */
    @RequestMapping(value = ERROR_PATH)
    @ResponseBody
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        Map<String, Object> body = getErrorAttributes(request, getTraceParameter(request));
        HttpStatus status = getStatus(request);
        return new ResponseEntity<Map<String, Object>>(body, status);
    }

    /**
     * Returns the path of the error page.
     *
     * @return the error path
     */
    @Override
    public String getErrorPath() {
        return ERROR_PATH;
    }


    private boolean getTraceParameter(HttpServletRequest request) {
        String parameter = request.getParameter("trace");
        if (parameter == null) {
            return false;
        }
        return !"false".equals(parameter.toLowerCase());
    }

    private Map<String, Object> getErrorAttributes(HttpServletRequest request,
                                                   boolean includeStackTrace) {
        RequestAttributes requestAttributes = new ServletRequestAttributes(request);
        return this.errorAttributes.getErrorAttributes(requestAttributes,
                includeStackTrace);
    }

    private HttpStatus getStatus(HttpServletRequest request) {
        Integer statusCode = (Integer) request
                .getAttribute("javax.servlet.error.status_code");
        if (statusCode != null) {
            try {
                return HttpStatus.valueOf(statusCode);
            }
            catch (Exception ex) {
            }
        }
        return HttpStatus.INTERNAL_SERVER_ERROR;
    }
}

La clase anterior se basa en la clase Springs BasicErrorController .

Puede crear una instancia de lo anterior de ErrorControlleresta manera en un @Configurationarchivo:

 @Autowired
 private ErrorAttributes errorAttributes;

 @Bean
 public AppErrorController appErrorController(){return new AppErrorController(errorAttributes);}

Puede elegir anular el valor predeterminado ErrorAttributesimplementando ErrorAttributes . Pero en la mayoría de los casos, los DefaultErrorAttributes deberían ser suficientes.


1
Su enlace a la BasicErrorControllerclase 404.
Stephane

@owaism: El enlace de BasicErrorControllerya no es bueno, ¿puedes actualizar?
HDJEMAI

1
El enlace a BasicErrorControllerestá arreglado ahora.
axiopisty

15

En mi caso, la clase de controlador se anotó con @Controller. Cambiar eso para @RestControllerresolver el problema. Básicamente, @RestControllerse @Controller + @ResponseBody usa @RestControllero @Controllercon @ResponseBodyanotaciones con cada método.

Algunas notas útiles aquí: https://www.genuitec.com/spring-frameworkrestcontroller-vs-controller/


Funciona, pero según los ejemplos, toda la configuración básica de Internet debería funcionar con @Controller. ¿Algún organismo consciente de esta razón por la que solo funciona RestController?
supernova

Al anotar su clase con @RestControllerella, implícitamente agrega la @ResponseBodyanotación, pero si está usando la @Controlleranotación, debe agregar explícitamente esta anotación usted mismo.
Robin Keskisarkka

10

en mi caso, debido a la posición del paquete, lo que significa que el paquete del controlador debe estar por encima del paquete de clase principal

si mi paquete de clase principal es package co.companyname.spring.tutorial;cualquier paquete de controlador deberíapackage co.companyname.spring.tutorial.WHAT_EVER_HERE;

package co.companyname.spring.tutorial; // package for main class
@SpringBootApplication
public class FirstProjectApplication {

    public static void main(String[] args) {
        SpringApplication.run(FirstProjectApplication.class, args);
    }
}


package co.companyname.spring.tutorial.controllers; // package for controllers 

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController 
public class HelloController { 

@RequestMapping("/hello")  
public String hello() {   
 return "Hello, world"; 
 }}

después de terminar la codificación, presione el tablero de arranque

ingrese la descripción de la imagen aquí

una última cosa para asegurarse de que su controlador esté mapeando o no solo la consola, debería ver algo smilliar

Mapped "{[/hello]}" onto public java.lang.String co.companyname.spring.tutorial.controllers.HelloController.hello()

codificación feliz


9

Esto sucede cuando no se define una página de error explícita. Para definir una página de error, cree una asignación de / error con una vista. por ejemplo, el código siguiente se asigna a un valor de cadena que se devuelve en caso de error.

package com.rumango.controller;

import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class IndexController implements ErrorController{
    private final static String PATH = "/error";
    @Override
    @RequestMapping(PATH)
    @ResponseBody
    public String getErrorPath() {
        // TODO Auto-generated method stub
        return "No Mapping Found";
    }

}

¿Puede agregar alguna explicación a su código? ¿Por qué resuelve la pregunta, cuáles son las partes cruciales?
Nico Haase

Hay una cosa específica a tener en cuenta en esta respuesta en relación con Spring Boot que me causó un poco de dolor de cabeza al principio. Es importante implementar la interfaz ErrorController de springframework. Si crea un punto final de controlador asignado a "/ error" sin hacer esto, obtendrá un error que le indicará que el método ya está asignado.
mmaynar1

8

Intente agregar la dependencia.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

2
¿Qué hace esto realmente?
Stealth Rabbi

Agregué esta dependencia y funcionó. Al igual que @StealthRabbi ... también me pregunto qué hace eso realmente.
twindham

@StealthRabbi Esto agrega una dependencia a un marco de trabajo de plantillas llamado Thymeleaf, que es una alternativa y el enfoque preferido para JSP. Esta respuesta no es una respuesta real de la OMI, lanzando alrededor de dependencias no ayuda a cualquiera que esté realmente interesado en el tema central
Cristiano

5

Agregué esta dependencia y resolvió mi problema.

<dependency>
    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

Esta es mi teoría: si estamos usando "@Controller", de alguna manera Spring requeriría que tuviéramos un determinado motor de plantillas. Y en este caso, Thymeleaf. Por lo tanto, se requiere spring-boot-starter-thymeleaf. Mientras que si usamos "@RestController", Spring Boot no requeriría un motor de plantilla. Y por lo tanto, funciona sin Thymeleaf.
Yosi Pramajaya

4

Estoy desarrollando la aplicación Spring Boot durante algunas semanas ... Y estaba obteniendo el mismo error que el siguiente;

Página de error de etiqueta blanca Esta aplicación no tiene un mapeo explícito para / error, por lo que está viendo esto como una alternativa. Jue 18 de enero 14:12:11 AST 2018 Hubo un error inesperado (tipo = No encontrado, estado = 404). No hay mensajes disponibles

Cuando recibo este mensaje de error, me di cuenta de que mi controlador o la clase de controlador de descanso están definidos en una nota en mi proyecto. Me refiero a que todos nuestros paquetes de controladores no son el mismo paquete con la clase principal que incluye la anotación @SpringBootApplication ... Quiero decir que debe agregar el nombre del paquete del controlador a la anotación @ComponentScan a su clase principal, que incluye la anotación @SpringBootApplication. a continuación, su problema se resolverá ... Lo más importante es que debe agregar el paquete de todos los controladores a la anotación @ComponentScan como lo hice en el siguiente

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan({ "com.controller.package1, com.controller.package2, com.controller.package3, com.controller.packageN", "controller", "service" } // If our Controller class or Service class is not in the same packages we have //to add packages's name like this...directory(package) with main class
public class MainApp {
    public static void main(String[] args) {
        SpringApplication.run(MainApp.class, args);
    }
}

Espero que estos códigos ayuden a alguien ...

Si encuentra otra forma de solucionar este error o tiene algunas sugerencias para mí, por favor escriba a los comentarios ... gracias ...


4

En la clase principal, después de la configuración "@SpringBootApplication", agregar "@ComponentScan" sin tener ningún argumento, funcionó para mí.

Clase principal :

@SpringBootApplication
@ComponentScan
public class CommentStoreApplication {

    public static void main(String[] args) {
        SpringApplication.run(CommentStoreApplication.class, args);

    }
}

Clase RestController:

@RestController
public class CommentStoreApp {

    @RequestMapping("/") 
    public String hello() {
        return "Hello World!";
    }
}

PD: no se pierda de ejecutar los comandos mvn clean y mvn install, antes de iniciar la aplicación


4

Bastante tarde para la fiesta. Según la documentación oficial de Spring, "Spring Boot instala una página de error de etiqueta blanca que se ve en un cliente de navegador si encuentra un error en el servidor". https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-customize-the-whitelabel-error-page

  1. Puede deshabilitar la función estableciendo server.error.whitelabel.enabled=falseen application.yml o en el archivo application.properties .

2. La forma recomendada es configurar su página de error para que el usuario final pueda entender. En la carpeta recursos / plantillas, cree un archivo error.html y agregue dependencia en el archivo pom.xml

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

Spring elegirá automáticamente la página error.html como la plantilla de error predeterminada. Nota: - No olvide actualizar el proyecto maven después de agregar la dependencia.


3

Es posible que reciba el error, es decir

"Esta aplicación no tiene un mapeo explícito para / error, por lo que está viendo esto como una alternativa".

Esto se debe a que no está escaneando sus clases de controlador y servicio que debe especificar en su clase main () de esta manera,

package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableAutoConfiguration
**@ComponentScan({"com.example.demo", "controller", "service"})**
public class SpringBootMvcExample1Application {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootMvcExample1Application.class, args);
    }
}

Nota: Aquí, he especificado varias clases como demostración, controlador y servicio para escanear, entonces solo funcionará correctamente.


3

Tienes que organizar los paquetes de modo que el paquete que contiene el main estático público (o donde escribiste @SpringBootApplication), sea el padre de todos tus otros paquetes.


- com.mypackage + nameApplication.java - com.mypachage.model - com.mypachage.controller - com.mypachage.dao
sakgeek

3

Por defecto, el arranque de primavera escaneará el paquete actual para la definición de bean. Entonces, si su paquete actual donde se define la clase principal y el paquete del controlador no es el mismo o el paquete del controlador no es el paquete secundario de su paquete de aplicación principal, no escaneará el controlador. Para resolver este problema, se puede incluir una lista de paquetes para la definición de frijoles en el paquete principal

@SpringBootApplication(scanBasePackages = {"com.module.restapi1.controller"})

o crear una jerarquía de paquete donde el paquete hijo se deriva del paquete principal

package com.module.restapi;
package com.module.restapi.controller

2

El problema es que está navegando a localhost: 8080 / en lugar de localhost: 8080 / upload como se indica en la guía. Spring Boot tiene una página de error predeterminada que se usa cuando navega a una ruta indefinida para evitar revelar detalles específicos del servidor (que pueden verse como un riesgo de seguridad).

Sus opciones son: visitar la página correcta, agregar su propia página de destino o anular la página de error blanca .

Para simplificar esta situación particular, actualicé la guía para que use / en lugar de / upload.


2

Sé que no es exactamente la respuesta a la pregunta, pero esta pregunta es la primera que aparece en Google :)

El problema ("Esta aplicación no tiene un mapeo explícito para / error") aparece al intentar acceder a la interfaz de usuario de Swagger.

En mi caso, los problemas fueron causados ​​por @RestController ("/ endpoint"), que no es manejado correctamente por swagger.

Entonces, esto resultó en errores:

@RestController("/endpoint")
public class EndpointController {

Y esto estuvo bien

@RestController
@RequestMapping("/endpoint")
public class EndpointController {

2

esto puede suceder si olvida la anotación @RestController en la parte superior de su clase de controlador import import org.springframework.web.bind.annotation.RestController;

y agregue la anotación como se muestra a continuación

consulte el ejemplo simple a continuación

import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;


@RestController
public class HelloController {
@RequestMapping("/")
    public String index() {
        return "Greetings from Spring Boot!";
    }

}

1

Cambie @Controller a @RestController en su clase de controlador y todo debería ir sin problemas.


1

Yo también obtuve el mismo error y pude resolver el error agregando la siguiente dependencia a mi pom.xml.

<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-jasper</artifactId>
    <scope>provided</scope>
</dependency>

La razón es que estamos usando JSP como vista. El contenedor de servlet integrado predeterminado para Spring Boot Starter Web es tomcat. Para habilitar el soporte para JSP, necesitaríamos agregar una dependencia en tomcat-embed-jasper.

En mi caso, estaba devolviendo un JSP como vista desde el controlador. Espero que esta respuesta ayude a alguien que está luchando con el mismo problema.


1

Estaba enfrentando el mismo problema, usando gradle y se resolvió al agregar las siguientes dependencias:

compile('org.springframework.boot:spring-boot-starter-data-jpa')
compile('org.springframework.boot:spring-boot-starter-web')
testCompile('org.springframework.boot:spring-boot-starter-test')
compile('org.apache.tomcat.embed:tomcat-embed-jasper')

antes me faltaba el último que causaba el mismo error.


Tuve el mismo problema y me faltaba el complemento tomcat-embed-jasper en pom.xml. Y tomcat-embed-jasper es importante para renderizar jsp.
rinilnath hace

boraji.com/… , esto conduce a encontrar que faltaba tomcat-embed-jasper
rinilnath hace

1

Estaba enfrentando este problema y luego me di cuenta de que me faltaba la @Configurationanotación en la MvcConfigclase que básicamente hace el mapeo para ViewControllersy setViewNames.

Aquí está el contenido del archivo:

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
**@Configuration**
public class MvcConfig implements WebMvcConfigurer{
   public void addViewControllers(ViewControllerRegistry registry)
   {
      registry.addViewController("/").setViewName("login");
      registry.addViewController("/login").setViewName("login");
      registry.addViewController("/dashboard").setViewName("dashboard");
   }
}

Espero que esto ayude a alguien !!


Esto lo hizo por mí.
Anthony Okoth

1

Asegúrese de tener jasper y jstl en la lista de dependencias:

<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-jasper</artifactId>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jstl</artifactId>
</dependency>

Aquí hay un proyecto inicial en funcionamiento: https://github.com/spring-projects/spring-boot/tree/master/spring-boot-samples/spring-boot-sample-web-jsp

Autor: Biju Kunjummen


1

Necesito mencionar de esta manera y dar la referencia a los paquetes y funcionó. Puede excluir @EnableAutoConfigurationesta anotación, pero es necesario que omita cualquier dependencia relacionada con la base de datos.

@SpringBootApplication
@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
@ComponentScan(basePackages = {"your package 1", "your package2"})

public class CommentStoreApplication {

    public static void main(String[] args) {
        SpringApplication.run(CommentStoreApplication.class, args);

    }
}

1

La clase principal debe estar fuera de la estructura de árbol de los paquetes de la aplicación. Por ejemplo: ejemplo


0

Todo lo que he hecho para resolver este tipo de problema es mencionar la anotación @Configuration en MVCConfig Class.

Como éste :

package com.example;

/**
 * Created by sartika.s.hasibuan on 1/10/2017.
 */
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@EnableAutoConfiguration
@Configuration
@ComponentScan
public class MvcConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/home").setViewName("home");
        registry.addViewController("/").setViewName("home");
        registry.addViewController("/hello").setViewName("hello");
        registry.addViewController("/login").setViewName("login");
    }

}

0

Tuve un error similar, utilizo el arranque y la velocidad de primavera, mi solución es verificar el archivo application.properties, spring.velocity.toolbox-config-location encontró que esta propiedad es incorrecta


0

En mi caso, este problema ocurre cuando se ejecuta SpringApplication desde IntelliJ después de ejecutarlo primero con maven.

Para resolver el problema, corro primero mvn clean. Luego ejecuto SpringApplication desde IntelliJ.


0

Asegúrese de que su Main. la clase debe estar encima de sus controladores. En el caso del siguiente ejemplo:

Main.class que contiene:

@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        SpringApplication.run(Main.class, args);
    }
}

EmployeeController. clase que contiene:

@RestController
public class EmployeeController {
    @InitBinder
    public void setAllowedFields(WebDataBinder dataBinder) {
        dataBinder.setDisallowedFields("id");
    }

    @RequestMapping(value = "/employee/save", method = RequestMethod.GET)
    public String save(){
        Employee newEmp = new Employee();
        newEmp.setAge(25);
        newEmp.setFirstName("Pikachu");
        newEmp.setId(100);
        return "Name: " + newEmp.getFirstName() + ", Age: " + newEmp.getAge() + ", Id = " + newEmp.getId();
    }
}

Si su clase principal está en la carpeta raíz, al igual que esta ruta: {nombre del proyecto} / src / main / java / main , asegúrese de que sus controladores estén debajo de su clase principal. Por ejemplo, {nombre de proyecto} / src / main / java / main / controllers .


0

En su archivo java (por ejemplo: Viper.java) que tenga la clase principal agregue: "@RestController" y @RequestMapping ("/")

@SpringBootApplication
@RestController
public class Viper {

  @RequestMapping("/")

   public String home(){
          return "This is what i was looking for";                      
     }

public static void main( String[] args){

   SpringApplication.run(Viper.class , args);
}

}

0

Verifique si ha marcado la clase del controlador con la anotación @RestController .

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.